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-8237] Support milestone-less Analysis in UI #342

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
4 changes: 4 additions & 0 deletions src/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ export const AppLayout = () => {
<Link to="/scm-repositories">SCM Repositories</Link>
</NavItem>

<NavItem isActive={pathname.includes('/deliverables-analysis')}>
<Link to="/deliverables-analysis">Deliverables Analyses</Link>
</NavItem>

<NavExpandable title="Insights" groupId="insights" isActive={pathname.includes('/product-milestone-comparison')}>
<NavItem
groupId="insights"
Expand Down
17 changes: 9 additions & 8 deletions src/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { BuildMetricsPage } from 'components/BuildMetricsPage/BuildMetricsPage';
import { BuildPages } from 'components/BuildPages/BuildPages';
import { BuildsPage } from 'components/BuildsPage/BuildsPage';
import { DashboardPage } from 'components/DashboardPage/DashboardPage';
import { DeliverablesAnalysesPage } from 'components/DeliverablesAnalysesPage/DeliverablesAnalysesPage';
import { DeliverablesAnalysisDetailPage } from 'components/DeliverablesAnalysisDetailPage/DeliverablesAnalysisDetailPage';
import { DemoPage } from 'components/DemoPage/DemoPage';
import { ErrorPage } from 'components/ErrorPage/ErrorPage';
import { GroupBuildDetailPage } from 'components/GroupBuildDetailPage/GroupBuildDetailPage';
Expand All @@ -36,8 +38,7 @@ import { ProductMilestoneCloseResultDetailPage } from 'components/ProductMilesto
import { ProductMilestoneCloseResultsPage } from 'components/ProductMilestoneCloseResultsPage/ProductMilestoneCloseResultsPage';
import { ProductMilestoneComparisonPage } from 'components/ProductMilestoneComparisonPage/ProductMilestoneComparisonPage';
import { ProductMilestoneCreateEditPage } from 'components/ProductMilestoneCreateEditPage/ProductMilestoneCreateEditPage';
import { ProductMilestoneDeliverablesAnalysisDetailPage } from 'components/ProductMilestoneDeliverablesAnalysisDetailPage/ProductMilestoneDeliverablesAnalysisDetailPage';
import { ProductMilestoneDeliverablesAnalysisPage } from 'components/ProductMilestoneDeliverablesAnalysisPage/ProductMilestoneDeliverablesAnalysisPage';
import { ProductMilestoneDeliverablesAnalysesPage } from 'components/ProductMilestoneDeliverablesAnalysesPage/ProductMilestoneDeliverablesAnalysesPage';
import { ProductMilestoneDeliveredArtifactsPage } from 'components/ProductMilestoneDeliveredArtifactsPage/ProductMilestoneDeliveredArtifactsPage';
import { ProductMilestoneDetailPage } from 'components/ProductMilestoneDetailPage/ProductMilestoneDetailPage';
import { ProductMilestoneInterconnectionGraphPage } from 'components/ProductMilestoneInterconnectionGraphPage/ProductMilestoneInterconnectionGraphPage';
Expand Down Expand Up @@ -150,17 +151,13 @@ export const AppRoutes = (
}
/>
<Route path="close-results/:closeResultId" index element={<ProductMilestoneCloseResultDetailPage />} />
<Route
path="deliverables-analysis/:deliverablesAnalysisId"
index
element={<ProductMilestoneDeliverablesAnalysisDetailPage />}
/>
<Route path="deliverables-analysis/:deliverablesAnalysisId" index element={<DeliverablesAnalysisDetailPage />} />
</Route>
<Route path=":productMilestoneId" element={<ProductMilestonePages />}>
<Route path="details" element={<ProductMilestoneDetailPage />} />
<Route path="builds-performed" element={<ProductMilestoneBuildsPerformedPage />} />
<Route path="close-results" element={<ProductMilestoneCloseResultsPage />} />
<Route path="deliverables-analysis" element={<ProductMilestoneDeliverablesAnalysisPage />} />
<Route path="deliverables-analysis" element={<ProductMilestoneDeliverablesAnalysesPage />} />
<Route path="delivered-artifacts" element={<ProductMilestoneDeliveredArtifactsPage />} />
<Route path="interconnection-graph" element={<ProductMilestoneInterconnectionGraphPage />} />
<Route index element={<Navigate to="details" replace />} />
Expand Down Expand Up @@ -242,6 +239,10 @@ export const AppRoutes = (
/>
<Route path=":scmRepositoryId" element={<ScmRepositoryDetailPage />} />
</Route>
<Route path="deliverables-analysis">
<Route index element={<DeliverablesAnalysesPage />} />
<Route path=":deliverablesAnalysisId" element={<DeliverablesAnalysisDetailPage />} />
</Route>

{/* special pages */}
<Route path="system">
Expand Down
1 change: 1 addition & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const PageTitles = {
productMilestoneComparison: 'Product Milestone Comparison',
scmRepositoryCreate: 'Create SCM Repository',
scmRepositoryEdit: 'Update SCM Repository',
deliverablesAnalyses: 'Deliverables Analyses',
administration: 'Administration',
pageNotFound: 'Page Not Found',
delimiterSymbol: '·',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface IExtendedDeliverableAnalyzerOperation extends DeliverableAnalyzerOpera
runAsScratchAnalysis: any;
}

export const productMilestoneDeliverablesAnalysisEntityAttributes = {
export const deliverablesAnalysisEntityAttributes = {
id: {
id: 'id',
title: 'ID',
Expand Down
2 changes: 1 addition & 1 deletion src/components/ActionModal/ActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const ActionModal = ({
actions={[
// TODO: NCL-8010
<Button variant="primary" onClick={onSubmit} isDisabled={isSubmitDisabled || serviceContainer?.loading}>
{serviceContainer && (!serviceContainer.error || serviceContainer.loading) && (
{serviceContainer && serviceContainer.loading && (
<ServiceContainerLoading variant="icon" {...serviceContainer} title={actionTitle} />
)}{' '}
{wasLastActionSuccessful && !wereSubmitDataChanged && <CheckIcon />} {actionTitle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';

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

import { productMilestoneDeliverablesAnalysisEntityAttributes } from 'common/productMilestoneDeliverablesAnalysisEntityAttributes';
import { deliverablesAnalysisEntityAttributes } from 'common/deliverablesAnalysisEntityAttributes';

import { IFieldConfigs, IFieldValues, useForm } from 'hooks/useForm';
import { useServiceContainer } from 'hooks/useServiceContainer';
Expand All @@ -12,6 +12,7 @@ import { ActionModal } from 'components/ActionModal/ActionModal';
import { FormInput } from 'components/FormInput/FormInput';
import { TooltipWrapper } from 'components/TooltipWrapper/TooltipWrapper';

import * as operationsApi from 'services/operationsApi';
import * as productMilestoneApi from 'services/productMilestoneApi';

import { validateUrls } from 'utils/formValidationHelpers';
Expand All @@ -26,56 +27,61 @@ const fieldConfigs = {
},
} satisfies IFieldConfigs;

interface IProductMilestoneAnalyzeDeliverablesModalProps {
interface IAnalyzeDeliverablesModalProps {
isModalOpen: boolean;
toggleModal: () => void;
productMilestone: ProductMilestone;
productMilestone?: ProductMilestone;
}

export const ProductMilestoneAnalyzeDeliverablesModal = ({
isModalOpen,
toggleModal,
productMilestone,
}: IProductMilestoneAnalyzeDeliverablesModalProps) => {
const serviceContainerProductMilestoneAnalyzeDeliverables = useServiceContainer(productMilestoneApi.analyzeDeliverables, 0);
export const AnalyzeDeliverablesModal = ({ isModalOpen, toggleModal, productMilestone }: IAnalyzeDeliverablesModalProps) => {
const serviceContainerAnalyzeDeliverables = useServiceContainer(
productMilestone ? productMilestoneApi.analyzeDeliverables : operationsApi.analyzeDeliverables,
0
);

const { register, getFieldState, getFieldErrors, handleSubmit, isSubmitDisabled, hasFormChanged } = useForm();

const confirmModal = (data: IFieldValues) => {
return serviceContainerProductMilestoneAnalyzeDeliverables
.run({
serviceData: {
const serviceData = productMilestone
? {
id: productMilestone.id,
data: {
deliverablesUrls: data.deliverablesUrls?.split(/\s+/),
runAsScratchAnalysis: data.runAsScratchAnalysis,
},
},
})
.catch((error) => {
console.error('Failed to analyze Deliverables.');
throw error;
});
}
: {
data: {
deliverablesUrls: data.deliverablesUrls?.split(/\s+/),
},
};

return serviceContainerAnalyzeDeliverables.run({ serviceData: serviceData as any }).catch((error) => {
console.error('Failed to analyze Deliverables.');
throw error;
});
};

return (
<ActionModal
modalTitle={`Analyze Deliverables: ${productMilestone.version}?`}
modalTitle={'Analyze Deliverables' + (productMilestone ? `: ${productMilestone.version}?` : '?')}
actionTitle="Analyze Deliverables"
isOpen={isModalOpen}
isSubmitDisabled={isSubmitDisabled}
wereSubmitDataChanged={hasFormChanged}
onToggle={toggleModal}
onSubmit={handleSubmit(confirmModal)}
serviceContainer={serviceContainerProductMilestoneAnalyzeDeliverables}
serviceContainer={serviceContainerAnalyzeDeliverables}
modalVariant="large"
onSuccessActions={[
<Button
variant="secondary"
// TODO: Make link absolute once Product data are available
component={(props: any) => (
<Link {...props} to={`deliverables-analysis/${serviceContainerProductMilestoneAnalyzeDeliverables.data?.id}`} />
)}
component={
productMilestone
? (props: any) => <Link {...props} to={`deliverables-analysis/${serviceContainerAnalyzeDeliverables.data?.id}`} />
: undefined
}
>
Open Deliverables Analysis details
</Button>,
Expand All @@ -88,58 +94,56 @@ export const ProductMilestoneAnalyzeDeliverablesModal = ({
>
<FormGroup
isRequired
label={productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.title}
fieldId={productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.id}
label={deliverablesAnalysisEntityAttributes.deliverablesUrls.title}
fieldId={deliverablesAnalysisEntityAttributes.deliverablesUrls.id}
helperText={
<FormHelperText
isHidden={getFieldState(productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.id) !== 'error'}
isHidden={getFieldState(deliverablesAnalysisEntityAttributes.deliverablesUrls.id) !== 'error'}
isError
>
{getFieldErrors(productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.id)}
{getFieldErrors(deliverablesAnalysisEntityAttributes.deliverablesUrls.id)}
</FormHelperText>
}
>
<TextArea
isRequired
type="text"
id={productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.id}
name={productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.id}
id={deliverablesAnalysisEntityAttributes.deliverablesUrls.id}
name={deliverablesAnalysisEntityAttributes.deliverablesUrls.id}
resizeOrientation="vertical"
autoResize
autoComplete="off"
placeholder={`https://url-path/to/file1.zip
https://url-path/to/file2.zip
https://url-path/to/file3.zip`}
{...register<string>(
productMilestoneDeliverablesAnalysisEntityAttributes.deliverablesUrls.id,
fieldConfigs.deliverablesUrls
)}
{...register<string>(deliverablesAnalysisEntityAttributes.deliverablesUrls.id, fieldConfigs.deliverablesUrls)}
/>
</FormGroup>
<FormGroup
label={productMilestoneDeliverablesAnalysisEntityAttributes.runAsScratchAnalysis.title}
fieldId={productMilestoneDeliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id}
labelIcon={
<TooltipWrapper tooltip={productMilestoneDeliverablesAnalysisEntityAttributes.runAsScratchAnalysis.tooltip} />
}
label={deliverablesAnalysisEntityAttributes.runAsScratchAnalysis.title}
fieldId={deliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id}
labelIcon={<TooltipWrapper tooltip={deliverablesAnalysisEntityAttributes.runAsScratchAnalysis.tooltip} />}
>
<FormInput<boolean>
{...register<boolean>(
productMilestoneDeliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id,
deliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id,
fieldConfigs.runAsScratchAnalysis
)}
render={({ value, onChange, onBlur }) => (
<Switch
id={productMilestoneDeliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id}
name={productMilestoneDeliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id}
id={deliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id}
name={deliverablesAnalysisEntityAttributes.runAsScratchAnalysis.id}
label="Scratch Option Enabled"
labelOff="Scratch Option Disabled"
isChecked={value}
isChecked={productMilestone ? value : true}
onChange={onChange}
onBlur={onBlur}
isDisabled={!productMilestone}
/>
)}
/>

<FormHelperText isHidden={!!productMilestone}>Milestone-less analyses run as scratch.</FormHelperText>
</FormGroup>
</Form>
</ActionModal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import { css } from '@patternfly/react-styles';

import { ProtectedComponent } from 'components/ProtectedContent/ProtectedComponent';

interface IProductMilestoneAnalyzeDeliverablesModalButtonProps {
interface IAnalyzeDeliverablesModalButtonProps {
toggleModal: () => void;
variant: 'detail' | 'list';
}

export const ProductMilestoneAnalyzeDeliverablesModalButton = ({
toggleModal,
variant,
}: IProductMilestoneAnalyzeDeliverablesModalButtonProps) => (
export const AnalyzeDeliverablesModalButton = ({ toggleModal, variant }: IAnalyzeDeliverablesModalButtonProps) => (
<ProtectedComponent>
<Button
variant={variant === 'list' ? 'plain' : 'tertiary'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Link } from 'react-router-dom';

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

import { deliverablesAnalysisEntityAttributes } from 'common/deliverablesAnalysisEntityAttributes';
import { getFilterOptions, getSortOptions } from 'common/entityAttributes';
import { productMilestoneDeliverablesAnalysisEntityAttributes } from 'common/productMilestoneDeliverablesAnalysisEntityAttributes';

import { IServiceContainerState } from 'hooks/useServiceContainer';
import { ISortOptions, useSorting } from 'hooks/useSorting';
Expand All @@ -21,27 +21,27 @@ import { Toolbar } from 'components/Toolbar/Toolbar';
import { ToolbarItem } from 'components/Toolbar/ToolbarItem';
import { Username } from 'components/Username/Username';

interface IProductMilestoneDeliverablesAnalysisListProps {
serviceContainerDeliverablesAnalysis: IServiceContainerState<DeliverableAnalyzerOperationPage>;
interface IDeliverablesAnalysesListProps {
serviceContainerDeliverablesAnalyses: IServiceContainerState<DeliverableAnalyzerOperationPage>;
componentId: string;
}

/**
* Component displaying list of Deliverables Analysis.
* Component displaying list of Deliverables Analyses.
*
* @param serviceContainerDeliverablesAnalysis - Service Container for Deliverables Analysis
* @param serviceContainerDeliverablesAnalyses - Service Container for Deliverables Analyses
* @param componentId - Component ID
*/
export const ProductMilestoneDeliverablesAnalysisList = ({
serviceContainerDeliverablesAnalysis,
export const DeliverablesAnalysesList = ({
serviceContainerDeliverablesAnalyses,
componentId,
}: IProductMilestoneDeliverablesAnalysisListProps) => {
}: IDeliverablesAnalysesListProps) => {
const sortOptions: ISortOptions = useMemo(
() =>
getSortOptions({
entityAttributes: productMilestoneDeliverablesAnalysisEntityAttributes,
entityAttributes: deliverablesAnalysisEntityAttributes,
defaultSorting: {
attribute: productMilestoneDeliverablesAnalysisEntityAttributes.submitTime.id,
attribute: deliverablesAnalysisEntityAttributes.submitTime.id,
direction: 'desc',
},
}),
Expand All @@ -55,33 +55,30 @@ export const ProductMilestoneDeliverablesAnalysisList = ({
<Toolbar>
<ToolbarItem>
<Filtering
filterOptions={useMemo(
() => getFilterOptions({ entityAttributes: productMilestoneDeliverablesAnalysisEntityAttributes }),
[]
)}
filterOptions={useMemo(() => getFilterOptions({ entityAttributes: deliverablesAnalysisEntityAttributes }), [])}
componentId={componentId}
/>
</ToolbarItem>
</Toolbar>

<ContentBox borderTop>
<ServiceContainerLoading {...serviceContainerDeliverablesAnalysis} title="Deliverables Analysis">
<ServiceContainerLoading {...serviceContainerDeliverablesAnalyses} title="Deliverables Analyses">
<TableComposable isStriped variant="compact">
<Thead>
<Tr>
<Th width={20}>{productMilestoneDeliverablesAnalysisEntityAttributes.id.title}</Th>
<Th width={20}>{productMilestoneDeliverablesAnalysisEntityAttributes.progressStatus.title}</Th>
<Th width={20}>{productMilestoneDeliverablesAnalysisEntityAttributes.result.title}</Th>
<Th width={20}>{deliverablesAnalysisEntityAttributes.id.title}</Th>
<Th width={20}>{deliverablesAnalysisEntityAttributes.progressStatus.title}</Th>
<Th width={20}>{deliverablesAnalysisEntityAttributes.result.title}</Th>
<Th width={20} sort={getSortParams(sortOptions.sortAttributes['submitTime'].id)}>
{productMilestoneDeliverablesAnalysisEntityAttributes.submitTime.title}
{deliverablesAnalysisEntityAttributes.submitTime.title}
</Th>
<Th width={20} sort={getSortParams(sortOptions.sortAttributes['user.username'].id)}>
{productMilestoneDeliverablesAnalysisEntityAttributes['user.username'].title}
{deliverablesAnalysisEntityAttributes['user.username'].title}
</Th>
</Tr>
</Thead>
<Tbody>
{serviceContainerDeliverablesAnalysis.data?.content?.map((operation, rowIndex) => (
{serviceContainerDeliverablesAnalyses.data?.content?.map((operation, rowIndex) => (
<Tr key={rowIndex}>
<Td>
{/* TODO: Make link absolute once Product data are available */}
Expand All @@ -102,7 +99,7 @@ export const ProductMilestoneDeliverablesAnalysisList = ({
</ServiceContainerLoading>
</ContentBox>

<Pagination componentId={componentId} count={serviceContainerDeliverablesAnalysis.data?.totalHits} />
<Pagination componentId={componentId} count={serviceContainerDeliverablesAnalyses.data?.totalHits} />
</>
);
};
Loading