diff --git a/plugins/cad/src/components/PackageRevisionPage/PackageRevisionPage.tsx b/plugins/cad/src/components/PackageRevisionPage/PackageRevisionPage.tsx index 7b63020b..c911f812 100644 --- a/plugins/cad/src/components/PackageRevisionPage/PackageRevisionPage.tsx +++ b/plugins/cad/src/components/PackageRevisionPage/PackageRevisionPage.tsx @@ -30,7 +30,7 @@ import { } from '@material-ui/core'; import Alert, { Color } from '@material-ui/lab/Alert'; import { cloneDeep } from 'lodash'; -import React, { Fragment, useEffect, useState } from 'react'; +import React, { Fragment, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { configAsDataApiRef } from '../../apis'; @@ -62,6 +62,7 @@ import { getNextPackageRevisionResource, getPackageRevision, getPackageRevisionTitle, + getUpgradePackageRevisionResource, getUpstreamPackageRevisionDetails, isLatestPublishedRevision, sortByPackageNameAndRevisionComparison, @@ -76,6 +77,7 @@ import { isDeploymentRepository, } from '../../utils/repository'; import { getRepositorySummary } from '../../utils/repositorySummary'; +import { toLowerCase } from '../../utils/string'; import { ConfirmationDialog, Select } from '../Controls'; import { PackageLink, RepositoriesLink, RepositoryLink } from '../Links'; import { AdvancedPackageRevisionOptions } from './components/AdvancedPackageRevisionOptions'; @@ -146,6 +148,10 @@ export const PackageRevisionPage = ({ mode }: PackageRevisionPageProps) => { const [openRestoreDialog, setOpenRestoreDialog] = useState(false); + const [isUpgradeAvailable, setIsUpgradeAvailable] = useState(false); + + const latestPublishedUpstream = useRef(); + const loadRepositorySummary = async (): Promise => { const thisRepositorySummary = await getRepositorySummary( api, @@ -207,23 +213,41 @@ export const PackageRevisionPage = ({ mode }: PackageRevisionPageProps) => { }); } - const upstream = getUpstreamPackageRevisionDetails(thisPackageRevision); + let upgradeAvailable = false; - if (upstream) { - const upstreamPackage = findPackageRevision( - thisPackageRevisions, - upstream.packageName, - upstream.revision, - ); + if (isLatestPublishedRevision(thisPackageRevision)) { + const upstream = getUpstreamPackageRevisionDetails(thisPackageRevision); - if (upstreamPackage) { - diffItems.push({ - label: `Upstream (${getPackageRevisionTitle(upstreamPackage)})`, - value: upstreamPackage.metadata.name, - }); + if (upstream) { + const upstreamPackage = findPackageRevision( + thisPackageRevisions, + upstream.packageName, + upstream.revision, + ); + + if (upstreamPackage) { + diffItems.push({ + label: `Upstream (${getPackageRevisionTitle(upstreamPackage)})`, + value: upstreamPackage.metadata.name, + }); + } + + const allUpstreamRevisions = filterPackageRevisions( + thisPackageRevisions, + upstream.packageName, + ); + latestPublishedUpstream.current = + findLatestPublishedRevision(allUpstreamRevisions); + + if ( + upstream.revision !== latestPublishedUpstream.current?.spec.revision + ) { + upgradeAvailable = true; + } } } + setIsUpgradeAvailable(upgradeAvailable); setSelectDiffItems(diffItems); const isPublished = @@ -436,6 +460,33 @@ export const PackageRevisionPage = ({ mode }: PackageRevisionPageProps) => { } }; + const createUpgradeRevision = async (): Promise => { + setUserInitiatedApiRequest(true); + + try { + if (!latestPublishedUpstream.current) { + throw new Error('The latest published upstream package is not defined'); + } + + const blueprintPackageRevisionName = + latestPublishedUpstream.current.metadata.name; + + const requestPackageRevision = getUpgradePackageRevisionResource( + packageRevision, + blueprintPackageRevisionName, + ); + + const newPackageRevision = await api.createPackageRevision( + requestPackageRevision, + ); + const newPackageName = newPackageRevision.metadata.name; + + navigate(packageRef({ repositoryName, packageName: newPackageName })); + } finally { + setUserInitiatedApiRequest(false); + } + }; + const createNewRevision = async (): Promise => { setUserInitiatedApiRequest(true); @@ -671,6 +722,20 @@ export const PackageRevisionPage = ({ mode }: PackageRevisionPageProps) => { if (isLatestPublishedPackageRevision) { if (latestRevision === latestPublishedRevision) { + if (isUpgradeAvailable) { + options.push( + + Upgrade to Latest Blueprint + , + ); + } + options.push( { const isViewMode = mode === PackageRevisionPageMode.VIEW; + const getUpgradeAlertText = (): string => { + const latestRevision = packageRevisions[0]; + + const blueprintName = `${latestPublishedUpstream.current?.spec.packageName} blueprint`; + const baseUpgradeText = `The ${blueprintName} has been upgraded.`; + + const latestRevisionUpstream = + getUpstreamPackageRevisionDetails(latestRevision); + + if (latestRevision !== packageRevision) { + const isLatestRevisionUpgraded = + latestRevisionUpstream?.revision === + latestPublishedUpstream.current?.spec.revision; + + const pendingRevisionName = `${latestRevision.spec.packageName} ${ + latestRevision.spec.revision + } ${toLowerCase(latestRevision.spec.lifecycle)} revision`; + + if (isLatestRevisionUpgraded) { + return `${baseUpgradeText} The ${pendingRevisionName} includes changes from the upgraded ${blueprintName}.`; + } + + return `${baseUpgradeText} The ${pendingRevisionName} does not include changes from the upgraded ${blueprintName}. The revision must be either published or deleted first before changes from the upgraded ${blueprintName} can be pulled in.`; + } + + return `${baseUpgradeText} Use the 'Upgrade to Latest Blueprint' button to create a revision that pulls in changes from the upgraded blueprint.`; + }; + return (
@@ -820,6 +913,13 @@ export const PackageRevisionPage = ({ mode }: PackageRevisionPageProps) => { label: 'Resources', content: ( + {isUpgradeAvailable && ( + + + {getUpgradeAlertText()} + + + )} { if (draftPackages) { summary = `${summary}, ${draftPackages} Draft`; } + /* + const upgradePackages = packageSummaries.filter(summary => summary.isUpgradeAvailable).length; + if (upgradePackages) { + summary = `${summary}, Upgrades Avaialble` + } +*/ return summary; }; diff --git a/plugins/cad/src/components/RepositoryPage/components/PackageSummaryTable.tsx b/plugins/cad/src/components/RepositoryPage/components/PackageSummaryTable.tsx index 0444b312..d207d5da 100644 --- a/plugins/cad/src/components/RepositoryPage/components/PackageSummaryTable.tsx +++ b/plugins/cad/src/components/RepositoryPage/components/PackageSummaryTable.tsx @@ -31,6 +31,7 @@ import { isDeploymentRepository } from '../../../utils/repository'; import { IconButton, PackageIcon } from '../../Controls'; import { PackageLink } from '../../Links'; import { SyncStatusVisual } from './SyncStatusVisual'; +import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; type PackageRevisionsTableProps = { title: string; @@ -61,6 +62,7 @@ type PackageSummaryRow = { upstreamPackageRevision?: PackageRevision; navigate: () => void; unpublished?: UnpublishedPackageRevision; + isUpgradeAvailable?: boolean; }; type NavigateToPackageRevision = (revision: PackageRevision) => void; @@ -70,20 +72,33 @@ const renderStatusColumn = ( ): JSX.Element => { const unpublishedRevision = thisPackageRevisionRow.unpublished; + const elements: JSX.Element[] = []; + + if (thisPackageRevisionRow.isUpgradeAvailable) { + elements.push( + + + , + ); + } + if (unpublishedRevision) { - return ( + elements.push( unpublishedRevision.navigate()} > - + , ); } - return ; + return ( +
+ {...elements} +
+ ); }; const renderBlueprintColumn = (row: PackageSummaryRow): JSX.Element => { @@ -189,6 +204,7 @@ const mapToPackageSummaryRow = ( upstreamPackageDisplayName: packageSummary.upstreamPackageName ? `${packageSummary.upstreamPackageName} ${packageSummary.upstreamPackageRevision}` : undefined, + isUpgradeAvailable: packageSummary.isUpgradeAvailable, upstreamPackageRevision: packageSummary.upstreamRevision, unpublished: mapToUnpublishedRevision( packageSummary, diff --git a/plugins/cad/src/utils/packageRevision.ts b/plugins/cad/src/utils/packageRevision.ts index a22bb26c..1dadedb6 100644 --- a/plugins/cad/src/utils/packageRevision.ts +++ b/plugins/cad/src/utils/packageRevision.ts @@ -213,6 +213,37 @@ export const getNextPackageRevisionResource = ( return resource; }; +export const getUpgradePackageRevisionResource = ( + currentRevision: PackageRevision, + upgradePackageRevisionName: string, +): PackageRevision => { + const { repository, packageName, revision, tasks } = currentRevision.spec; + const nextRevision = getNextRevision(revision); + + const [firstTask, ...remainderTasks] = tasks; + + if (firstTask.type !== 'clone') { + throw new Error( + `First task of a package revision to be upgraded must be of type 'clone'`, + ); + } + + const newTasks = [ + getCloneTask(upgradePackageRevisionName), + ...remainderTasks, + ]; + + const resource = getPackageRevisionResource( + repository, + packageName, + nextRevision, + PackageRevisionLifecycle.DRAFT, + newTasks, + ); + + return resource; +}; + export const sortByPackageNameAndRevisionComparison = ( packageRevision1: PackageRevision, packageRevision2: PackageRevision, diff --git a/plugins/cad/src/utils/packageSummary.ts b/plugins/cad/src/utils/packageSummary.ts index b2985dca..613c6d5f 100644 --- a/plugins/cad/src/utils/packageSummary.ts +++ b/plugins/cad/src/utils/packageSummary.ts @@ -21,6 +21,7 @@ import { RepositorySummary } from '../types/RepositorySummary'; import { RootSync } from '../types/RootSync'; import { findRootSyncForPackage } from './configSync'; import { + filterPackageRevisions, findLatestPublishedRevision, findPackageRevision, getUpstreamPackageRevisionDetails, @@ -37,6 +38,8 @@ export type PackageSummary = { upstreamRevision?: PackageRevision; upstreamPackageName?: string; upstreamPackageRevision?: string; + upstreamLatestPublishedRevision?: PackageRevision; + isUpgradeAvailable?: boolean; sync?: RootSync; }; @@ -91,6 +94,15 @@ export const getPackageSummariesForRepository = ( upstream.packageName, upstream.revision, ); + + thisPackageSummary.upstreamLatestPublishedRevision = + findLatestPublishedRevision( + filterPackageRevisions(upstreamRevisions, upstream.packageName), + ); + + thisPackageSummary.isUpgradeAvailable = + thisPackageSummary.upstreamLatestPublishedRevision?.spec.revision !== + upstream.revision; } return thisPackageSummary;