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-7204] Implement BuildConfigList page #261

Merged
merged 2 commits into from
Aug 8, 2023
Merged
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
69 changes: 69 additions & 0 deletions src/common/buildConfigEntityAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Build, BuildConfiguration } from 'pnc-api-types-ts';

import { TEntityAttributes } from 'common/entityAttributes';

const buildTypeValues: BuildConfiguration['buildType'][] = ['MVN', 'NPM', 'GRADLE', 'SBT'];

interface IExtendedBuildConfig extends BuildConfiguration {
buildStatus: Build['status'];
actions: any;
'project.name': string;
}

export const buildConfigEntityAttributes = {
id: {
id: 'id',
title: 'ID',
},
name: {
id: 'name',
title: 'Name',
filter: {
operator: '=like=',
},
sort: {},
},
description: {
id: 'description',
title: 'Description',
},
buildType: {
id: 'buildType',
title: 'Build Type',
values: buildTypeValues,
filter: {
operator: '==',
},
sort: {},
},
'project.name': {
id: 'project.name',
title: 'Project',
filter: {
operator: '=like=',
},
sort: {},
},
creationTime: {
id: 'creationTime',
title: 'Created',
sort: {
group: 'times',
},
},
modificationTime: {
id: 'modificationTime',
title: 'Modified',
sort: {
group: 'times',
},
},
buildStatus: {
id: 'buildStatus',
title: 'Latest Build Status',
},
actions: {
id: 'actions',
title: 'Actions',
},
} as const satisfies TEntityAttributes<IExtendedBuildConfig>;
2 changes: 1 addition & 1 deletion src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const PageTitles = {
productVersions: 'Product Versions',
productMilestones: 'Product Milestones',
productReleases: 'Product Releases',
buildConfig: 'Build Configs',
buildConfigs: 'Build Configs',
groupConfigs: 'Group Configs',
builds: 'Builds',
groupBuilds: 'Group Builds',
Expand Down
13 changes: 13 additions & 0 deletions src/components/BuildConfigLink/BuildConfigLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Link } from 'react-router-dom';

interface IBuildConfigLinkProps {
id: string;
children?: React.ReactNode;
}

/**
* Represents a link to a BuildConfigDetailPage of a specific BuildConfig.
*
* @param id - id of the BuildConfig to link to
*/
export const BuildConfigLink = ({ id, children }: IBuildConfigLinkProps) => <Link to={id}>{children}</Link>;
14 changes: 14 additions & 0 deletions src/components/BuildConfigLink/__tests__/BuildConfigLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

import { BuildConfigLink } from 'components/BuildConfigLink/BuildConfigLink';

describe('display BuildConfigLink component', () => {
test('renders BuildConfigLink', () => {
render(
<MemoryRouter>
<BuildConfigLink id="555" />
</MemoryRouter>
);
});
});
217 changes: 217 additions & 0 deletions src/components/BuildConfigsList/BuildConfigsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from '@patternfly/react-core';
import { TableComposable, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { useMemo, useState } from 'react';

import { BuildConfigurationWithLatestBuild } from 'pnc-api-types-ts';

import { buildConfigEntityAttributes } from 'common/buildConfigEntityAttributes';
import { PageTitles } from 'common/constants';
import { getFilterAttributes, getSortOptions } from 'common/entityAttributes';

import { IServiceContainer } from 'hooks/useServiceContainer';
import { ISortOptions, useSorting } from 'hooks/useSorting';

import { BuildConfigLink } from 'components/BuildConfigLink/BuildConfigLink';
import { BuildStartButton } from 'components/BuildStartButton/BuildStartButton';
import { ContentBox } from 'components/ContentBox/ContentBox';
import { Filtering, IDefaultFiltering } from 'components/Filtering/Filtering';
import { BuildConfigBuildTypeLabelMapper } from 'components/LabelMapper/BuildConfigBuildTypeLabelMapper';
import { Pagination } from 'components/Pagination/Pagination';
import { ProjectLink } from 'components/ProjectLink/ProjectLink';
import { PROTECTED_TYPE, ProtectedContent } from 'components/ProtectedContent/ProtectedContent';
import { ServiceContainerLoading } from 'components/ServiceContainers/ServiceContainerLoading';
import { SortGroup } from 'components/SortGroup/SortGroup';
import { Toolbar } from 'components/Toolbar/Toolbar';
import { ToolbarItem } from 'components/Toolbar/ToolbarItem';
import { Username } from 'components/Username/Username';

import { areDatesEqual, checkColumnsCombinations, createDateTime } from 'utils/utils';

const defaultFiltering: IDefaultFiltering = {
attribute: buildConfigEntityAttributes.name.id,
};

interface IBuildConfigsListProps {
serviceContainerBuildConfigs: IServiceContainer;
columns?: Array<keyof typeof buildConfigEntityAttributes>;
componentId: string;
}

const defaultColumns = [
buildConfigEntityAttributes.name.id,
buildConfigEntityAttributes.buildType.id,
buildConfigEntityAttributes['project.name'].id,
buildConfigEntityAttributes.creationTime.id,
buildConfigEntityAttributes.modificationTime.id,
buildConfigEntityAttributes.buildStatus.id,
];

/**
* Component displaying list of BuildConfigs.
*
* @param serviceContainerBuildConfigs - Service Container for BuildConfigs
* @param columns - The columns to be displayed
* @param componentId - Component ID
*/
export const BuildConfigsList = ({
serviceContainerBuildConfigs,
columns = defaultColumns,
componentId,
}: IBuildConfigsListProps) => {
const [isTimesSortDropdownOpen, setIsTimesSortDropdownOpen] = useState<boolean>(false);

const sortOptions: ISortOptions = useMemo(
() =>
getSortOptions({
entityAttributes: buildConfigEntityAttributes,
defaultSorting: {
attribute: buildConfigEntityAttributes.name.id,
direction: 'asc',
},
customColumns: defaultColumns,
}),
[]
);

const { getSortParams, getSortGroupParams } = useSorting(sortOptions, componentId);

checkColumnsCombinations({
columns,
combinations: [[buildConfigEntityAttributes.creationTime.id, buildConfigEntityAttributes.modificationTime.id]],
});

return (
<>
<Toolbar>
<ToolbarItem>
<Filtering
filterOptions={getFilterAttributes(buildConfigEntityAttributes, defaultColumns)}
componentId={componentId}
defaultFiltering={defaultFiltering}
/>
</ToolbarItem>
</Toolbar>

<ContentBox borderTop>
<ServiceContainerLoading {...serviceContainerBuildConfigs} title={PageTitles.buildConfigs}>
<TableComposable isStriped variant="compact">
<Thead>
<Tr>
{columns.includes(buildConfigEntityAttributes.name.id) && (
<Th width={25} sort={getSortParams(sortOptions.sortAttributes.name.id)}>
{buildConfigEntityAttributes.name.title}
</Th>
)}
{columns.includes(buildConfigEntityAttributes.description.id) && (
<Th width={15}>{buildConfigEntityAttributes.description.title}</Th>
)}
{columns.includes(buildConfigEntityAttributes.buildType.id) && (
<Th width={10} sort={getSortParams(sortOptions.sortAttributes.buildType.id)}>
{buildConfigEntityAttributes.buildType.title}
</Th>
)}
{columns.includes(buildConfigEntityAttributes['project.name'].id) && (
<Th width={15} sort={getSortParams(sortOptions.sortAttributes['project.name'].id)}>
{buildConfigEntityAttributes['project.name'].title}
</Th>
)}
{columns.includes(buildConfigEntityAttributes.creationTime.id) &&
columns.includes(buildConfigEntityAttributes.modificationTime.id) && (
<Th width={25} className="overflow-visible">
<SortGroup
title="Times"
sort={getSortGroupParams(sortOptions.sortAttributes.creationTime.id)}
isDropdownOpen={isTimesSortDropdownOpen}
onDropdownToggle={() => setIsTimesSortDropdownOpen(!isTimesSortDropdownOpen)}
/>
</Th>
)}
{columns.includes(buildConfigEntityAttributes.actions.id) && (
<ProtectedContent type={PROTECTED_TYPE.Component}>
<Th width={15}>{buildConfigEntityAttributes.actions.title}</Th>
</ProtectedContent>
)}
</Tr>
</Thead>
<Tbody>
{serviceContainerBuildConfigs.data?.content.map(
(buildConfig: BuildConfigurationWithLatestBuild, rowIndex: number) => (
<Tr key={rowIndex}>
{columns.includes(buildConfigEntityAttributes.name.id) && (
<Td>
<BuildConfigLink id={buildConfig.id}>{buildConfig.name}</BuildConfigLink>
</Td>
)}
{columns.includes(buildConfigEntityAttributes.description.id) && <Td>{buildConfig.description}</Td>}
{columns.includes(buildConfigEntityAttributes.buildType.id) && (
<Td>
<BuildConfigBuildTypeLabelMapper buildType={buildConfig.buildType} />
</Td>
)}
{columns.includes(buildConfigEntityAttributes['project.name'].id) && (
<Td>
{buildConfig.project && <ProjectLink id={buildConfig.project.id}>{buildConfig.project.name}</ProjectLink>}
</Td>
)}
{columns.includes(buildConfigEntityAttributes.creationTime.id) &&
columns.includes(buildConfigEntityAttributes.modificationTime.id) && (
<Td>
<DescriptionList className="gap-0" isHorizontal isCompact>
<DescriptionListGroup>
<DescriptionListTerm>{buildConfigEntityAttributes.creationTime.title}</DescriptionListTerm>
<DescriptionListDescription>
{buildConfig.creationTime && createDateTime({ date: buildConfig.creationTime }).custom}
{buildConfig.creationUser?.username && (
<span>
{' '}
by{' '}
<b>
<Username text={buildConfig.creationUser.username} />
</b>
</span>
)}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{buildConfigEntityAttributes.modificationTime.title}</DescriptionListTerm>
<DescriptionListDescription>
{buildConfig.modificationTime &&
createDateTime({
date: buildConfig.modificationTime,
includeDateInCustom:
!buildConfig.creationTime ||
!areDatesEqual(buildConfig.modificationTime, buildConfig.creationTime),
}).custom}
{buildConfig.modificationUser?.username && (
<span>
{' '}
by{' '}
<b>
<Username text={buildConfig.modificationUser.username} />
</b>
</span>
)}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</Td>
)}
{columns.includes(buildConfigEntityAttributes.actions.id) && (
<Td>
<ProtectedContent type={PROTECTED_TYPE.Component}>
<BuildStartButton buildConfig={buildConfig} size="sm" />
</ProtectedContent>
</Td>
)}
</Tr>
)
)}
</Tbody>
</TableComposable>
</ServiceContainerLoading>
</ContentBox>

<Pagination componentId={componentId} count={serviceContainerBuildConfigs.data?.totalHits} />
</>
);
};
45 changes: 29 additions & 16 deletions src/components/BuildConfigsPage/BuildConfigsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import { Divider, PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core';

import { PageTitles } from 'common/constants';

import { useQueryParamsEffect } from 'hooks/useQueryParamsEffect';
import { useServiceContainer } from 'hooks/useServiceContainer';
import { useTitle } from 'hooks/useTitle';

export const BuildConfigsPage = () => {
useTitle(PageTitles.buildConfig);
import { BuildConfigsList } from 'components/BuildConfigsList/BuildConfigsList';
import { PageLayout } from 'components/PageLayout/PageLayout';

import * as buildConfigApi from 'services/buildConfigApi';

interface IBuildConfigsPageProps {
componentId?: string;
}

export const BuildConfigsPage = ({ componentId = 'b1' }: IBuildConfigsPageProps) => {
const serviceContainerBuildConfigs = useServiceContainer(buildConfigApi.getBuildConfigsWithLatestBuild);

useQueryParamsEffect(serviceContainerBuildConfigs.run, { componentId });

useTitle(PageTitles.buildConfigs);

return (
<>
<PageSection variant={PageSectionVariants.light}>
<TextContent>
<Text component="h1">BuildConfigsPage</Text>
<Text component="p">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam quos unde, accusantium excepturi ad praesentium.
</Text>
</TextContent>
</PageSection>

<Divider component="div" />
</>
<PageLayout
title={PageTitles.buildConfigs}
description={
<>
This page contains all Build Configs. Build Config contains all the information required to build a particular Project
and it produces Build during the build process.
</>
}
>
<BuildConfigsList {...{ serviceContainerBuildConfigs, componentId }} />
</PageLayout>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

import { BuildConfigsPage } from 'components/BuildConfigsPage/BuildConfigsPage';

jest.mock('services/buildConfigApi');
jest.mock('services/keycloakService');
jest.mock('services/webConfigService');

test('renders BuildConfigsPage', () => {
render(<BuildConfigsPage />);
render(
<MemoryRouter>
<BuildConfigsPage />
</MemoryRouter>
);
});
Loading