diff --git a/ui/src/features/assemble-freight/artifact-menu-item.tsx b/ui/src/features/assemble-freight/artifact-menu-item.tsx index b13004d957..74624ee619 100644 --- a/ui/src/features/assemble-freight/artifact-menu-item.tsx +++ b/ui/src/features/assemble-freight/artifact-menu-item.tsx @@ -1,7 +1,13 @@ import classNames from 'classnames'; +import { SubscriptionName } from '@ui/features/common/subscription-name'; + import { DiscoveryResult } from './types'; -import { getSubscriptionKey, isEqualSubscriptions } from './unique-subscription-key'; +import { + getSubscriptionKey, + getSubscriptionName, + isEqualSubscriptions +} from './unique-subscription-key'; export interface ArtifactMenuItemProps { onClick: () => void; @@ -34,10 +40,16 @@ export const ArtifactMenuItems = ({ {items.map((item) => { const isSelected = !!selected && isEqualSubscriptions(selected, item); const key = getSubscriptionKey(item); + const subscriptionName = getSubscriptionName(item); return ( onClick(item)} selected={isSelected}> {key} + {!!subscriptionName && ( +
+ +
+ )}
); })} diff --git a/ui/src/features/assemble-freight/unique-subscription-key.ts b/ui/src/features/assemble-freight/unique-subscription-key.ts index bf77dd82f8..b221605701 100644 --- a/ui/src/features/assemble-freight/unique-subscription-key.ts +++ b/ui/src/features/assemble-freight/unique-subscription-key.ts @@ -22,5 +22,8 @@ export const getSubscriptionKeyFreight = (res: Image | Chart | GitCommit) => { return getSubscriptionKey(res); }; +export const getSubscriptionName = (res: DiscoveryResult) => + 'subscriptionName' in res ? res.subscriptionName : undefined; + export const isEqualSubscriptions = (a: DiscoveryResult, b: DiscoveryResult) => getSubscriptionKey(a) === getSubscriptionKey(b); diff --git a/ui/src/features/common/commit-info.tsx b/ui/src/features/common/commit-info.tsx index e7b35f8b57..1e12619e85 100644 --- a/ui/src/features/common/commit-info.tsx +++ b/ui/src/features/common/commit-info.tsx @@ -6,6 +6,12 @@ export const CommitInfo = ({ commit }: { commit: GitCommit }) => (
{commit.repoURL}
+ {commit.subscriptionName && ( + <> +
Subscription:
+
{commit.subscriptionName}
+ + )} {commit.branch ? ( <>
Branch:
diff --git a/ui/src/features/common/subscription-name.tsx b/ui/src/features/common/subscription-name.tsx new file mode 100644 index 0000000000..fb8e6f97b2 --- /dev/null +++ b/ui/src/features/common/subscription-name.tsx @@ -0,0 +1,18 @@ +import { Typography } from 'antd'; +import classNames from 'classnames'; + +export const SubscriptionName = ({ name, className }: { name?: string; className?: string }) => { + if (!name) { + return null; + } + + return ( + + {name} + + ); +}; diff --git a/ui/src/features/freight-timeline/freight-content-item.tsx b/ui/src/features/freight-timeline/freight-content-item.tsx index 6de5f5e671..c8b3d88374 100644 --- a/ui/src/features/freight-timeline/freight-content-item.tsx +++ b/ui/src/features/freight-timeline/freight-content-item.tsx @@ -5,6 +5,7 @@ import Link from 'antd/es/typography/Link'; import classNames from 'classnames'; import { useMemo } from 'react'; +import { SubscriptionName } from '../common/subscription-name'; import { TruncateMiddle } from '../common/truncate-middle'; export const FreightContentItem = (props: { @@ -23,6 +24,8 @@ export const FreightContentItem = (props: { artifactSource?: string; // build date of image artifactBuildDate?: string; + // name of the subscription that discovered the artifact, if it has one + subscriptionName?: string; }) => { const { horizontal, @@ -34,9 +37,22 @@ export const FreightContentItem = (props: { href, children, linkClass, - fullContentVisibility + fullContentVisibility, + subscriptionName } = props; + // The chips are too cramped to always carry the name, so it rides along in + // the tooltip and only becomes a visible tag where there is room for it. + const _title = useMemo(() => { + if (!subscriptionName) { + return title; + } + + return title + ? `${title} (subscription: ${subscriptionName})` + : `subscription: ${subscriptionName}`; + }, [title, subscriptionName]); + const _children = useMemo(() => { if (fullContentVisibility) { return children; @@ -57,7 +73,7 @@ export const FreightContentItem = (props: { 'bg-gray-200': !dark && horizontal })} overlay={overlay} - title={title} + title={_title} > {!!icon && ( @@ -107,6 +123,9 @@ export const FreightContentItem = (props: { {props.artifactBuildDate} )} + {fullContentVisibility && ( + + )} ); diff --git a/ui/src/features/freight-timeline/freight-contents.tsx b/ui/src/features/freight-timeline/freight-contents.tsx index 4a690dc552..698887e7f2 100644 --- a/ui/src/features/freight-timeline/freight-contents.tsx +++ b/ui/src/features/freight-timeline/freight-contents.tsx @@ -38,8 +38,9 @@ export const FreightContents = (props: { horizontal={horizontal} linkClass={linkClass} highlighted={highlighted} - key={c.id} + key={`${c.subscriptionName || ''}:${c.id}`} overlay={} + subscriptionName={c.subscriptionName} icon={faGitAlt} href={`${c.repoURL?.replace('.git', '')}/commit/${c.id}`} fullContentVisibility={props.fullContentVisibility} @@ -55,8 +56,9 @@ export const FreightContents = (props: { horizontal={horizontal} linkClass={linkClass} highlighted={highlighted} - key={`${i.repoURL}:${i.tag}`} + key={`${i.subscriptionName || ''}:${i.repoURL}:${i.tag}`} title={`${i.repoURL}:${i.tag}`} + subscriptionName={i.subscriptionName} icon={faDocker} href={urlForImage(i.repoURL || '')} fullContentVisibility={props.fullContentVisibility} @@ -72,8 +74,9 @@ export const FreightContents = (props: { horizontal={horizontal} linkClass={linkClass} highlighted={highlighted} - key={`${c.repoURL}:${c.version}`} + key={`${c.subscriptionName || ''}:${c.repoURL}:${c.version}`} title={`${c.repoURL}${c.name ? `/${c.name}` : ''}:${c.version}`} + subscriptionName={c.subscriptionName} fullContentVisibility={props.fullContentVisibility} icon={faAnchor} > @@ -84,6 +87,7 @@ export const FreightContents = (props: { ; + subscriptionName?: string; } | { type: 'git'; @@ -16,12 +17,14 @@ export type TableSource = author: string; committer: string; tag?: string; + subscriptionName?: string; } | { type: 'helm'; repoURL: string; name: string; version: string; + subscriptionName?: string; } | ({ type: 'other'; @@ -35,7 +38,8 @@ export const flattenFreightOrigin = ( type: 'image', repoURL: image?.repoURL || '', tag: image?.tag || '', - annotations: image?.annotations || {} + annotations: image?.annotations || {}, + subscriptionName: image?.subscriptionName })) || []; const git: TableSource[] = @@ -47,7 +51,8 @@ export const flattenFreightOrigin = ( committer: commit?.committer || '', id: commit?.id || '', message: commit?.message || '', - tag: commit?.tag || '' + tag: commit?.tag || '', + subscriptionName: commit?.subscriptionName })) || []; const helm: TableSource[] = @@ -55,7 +60,8 @@ export const flattenFreightOrigin = ( type: 'helm', repoURL: chart?.repoURL || '', name: chart?.name || '', - version: chart?.version || '' + version: chart?.version || '', + subscriptionName: chart?.subscriptionName })) || []; const other: TableSource[] = diff --git a/ui/src/features/project/pipelines/freight/freight-table.tsx b/ui/src/features/project/pipelines/freight/freight-table.tsx index 2fff4ec770..99982f8eda 100644 --- a/ui/src/features/project/pipelines/freight/freight-table.tsx +++ b/ui/src/features/project/pipelines/freight/freight-table.tsx @@ -6,6 +6,7 @@ import { Table } from 'antd'; import classNames from 'classnames'; import { useMemo } from 'react'; +import { SubscriptionName } from '@ui/features/common/subscription-name'; import { ArtifactMetadata } from '@ui/features/freight/artifact-metadata'; import { flattenFreightOrigin } from '@ui/features/freight/flatten-freight-origin-utils'; import { Freight } from '@ui/gen/api/v2/models'; @@ -55,12 +56,18 @@ export const FreightTable = (props: FreightTableProps) => { width: '30%', render: (_, record) => { if (record.type === 'other') { - return record.subscriptionName || '-'; + return '-'; } - return record.repoURL; + return {record.repoURL}; } }, + { + title: 'Subscription Name', + width: '15%', + render: (_, record) => + record.subscriptionName ? : '-' + }, { title: 'Version', render: (_, record) => { diff --git a/ui/src/features/project/pipelines/nodes/stage-freight.tsx b/ui/src/features/project/pipelines/nodes/stage-freight.tsx index 1ae286f9b6..a578aadd55 100644 --- a/ui/src/features/project/pipelines/nodes/stage-freight.tsx +++ b/ui/src/features/project/pipelines/nodes/stage-freight.tsx @@ -212,6 +212,9 @@ export const StageFreight = (props: { stage: Stage }) => { ); }; +const subscriptionTitle = (base: string | undefined, subscriptionName: string | undefined) => + subscriptionName ? `${base} (subscription: ${subscriptionName})` : base; + const Artifact = (props: { artifact: string | GitCommit | Chart | Image | ArtifactReference }) => { if (typeof props.artifact === 'string') { return ( @@ -221,10 +224,22 @@ const Artifact = (props: { artifact: string | GitCommit | Chart | Image | Artifa ); } + // A generic artifact has no repository to name it, so its subscription name + // is all the carousel can show alongside the version. if (isArtifactGeneric(props.artifact)) { return ( - - {props.artifact.version} + + +
{props.artifact.version}
+ + {!!props.artifact.subscriptionName && ( + {props.artifact.subscriptionName} + )} +
); } @@ -237,7 +252,11 @@ const Artifact = (props: { artifact: string | GitCommit | Chart | Image | Artifa const url = getGitCommitURL(props.artifact.repoURL || '', props.artifact.id || ''); let TagComponent = ( - +
{props.artifact.id?.slice(0, 7)}
@@ -273,7 +292,10 @@ const Artifact = (props: { artifact: string | GitCommit | Chart | Image | Artifa if (isArtifactChart(props.artifact)) { return ( @@ -297,7 +319,10 @@ const Artifact = (props: { artifact: string | GitCommit | Chart | Image | Artifa let TagComponent = ( diff --git a/ui/src/features/project/pipelines/nodes/subscription-node.tsx b/ui/src/features/project/pipelines/nodes/subscription-node.tsx index 9b40ccab67..4874159f20 100644 --- a/ui/src/features/project/pipelines/nodes/subscription-node.tsx +++ b/ui/src/features/project/pipelines/nodes/subscription-node.tsx @@ -23,8 +23,9 @@ export const SubscriptionNode = (props: { subscription: RepoSubscription }) => { props.subscription?.chart?.repoURL || props.subscription?.image?.repoURL || ''; - const title = humanComprehendableArtifact({ repoURL }) || props.subscription.name; - const base = artifactBase(repoURL) || repoURL; + const name = props.subscription.name; + const title = name || humanComprehendableArtifact({ repoURL }) || repoURL; + const base = name ? repoURL : artifactBase(repoURL) || repoURL; const link = artifactURL(repoURL); return { title, repoURL, base, link }; @@ -47,9 +48,11 @@ export const SubscriptionNode = (props: { subscription: RepoSubscription }) => { size='small' className={styles['subscription-node-size']} title={ - + {icon && } - {title} + + {title} + } variant='borderless' diff --git a/ui/src/features/project/pipelines/promotion/freight-comparison-table.tsx b/ui/src/features/project/pipelines/promotion/freight-comparison-table.tsx index 67c6125b44..d4c5d13bd8 100644 --- a/ui/src/features/project/pipelines/promotion/freight-comparison-table.tsx +++ b/ui/src/features/project/pipelines/promotion/freight-comparison-table.tsx @@ -2,10 +2,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Col, Flex, Row, Space, Table, Tag, theme, Typography } from 'antd'; import { useMemo } from 'react'; +import { SubscriptionName } from '@ui/features/common/subscription-name'; import { ArtifactMetadata } from '@ui/features/freight/artifact-metadata'; import { Freight, FreightReference } from '@ui/gen/api/v2/models'; -import { repoLabel, typeIcon, typeLabel, versionLabel } from './freight-comparison-utils'; +import { + repoLabel, + subscriptionName, + typeIcon, + typeLabel, + versionLabel +} from './freight-comparison-utils'; import { PairedRow, pairArtifacts, PairStatus } from './pair-artifacts'; const statusTag = (status: PairStatus) => { @@ -43,6 +50,11 @@ export const FreightComparisonTable = ({ const hasCurrent = !!currentFreight; + const hasSubscriptionNames = useMemo( + () => rows.some((row) => !!subscriptionName(row.incoming || row.current)), + [rows] + ); + return ( className={className} @@ -97,7 +109,7 @@ export const FreightComparisonTable = ({ > title='Repo / Name' - width='57%' + width={hasSubscriptionNames ? '42%' : '57%'} render={(_, row) => { const source = row.incoming || row.current; if (!source) { @@ -122,6 +134,21 @@ export const FreightComparisonTable = ({ ); }} /> + {hasSubscriptionNames && ( + + title='Name' + width='15%' + render={(_, row) => { + const name = subscriptionName(row.incoming || row.current); + + if (!name) { + return ; + } + + return ; + }} + /> + )} title='Current' width='15%' diff --git a/ui/src/features/project/pipelines/promotion/freight-comparison-utils.ts b/ui/src/features/project/pipelines/promotion/freight-comparison-utils.ts index 7c7d169df7..ffd92641f0 100644 --- a/ui/src/features/project/pipelines/promotion/freight-comparison-utils.ts +++ b/ui/src/features/project/pipelines/promotion/freight-comparison-utils.ts @@ -37,6 +37,9 @@ export const repoLabel = (source: TableSource): string => { return source.repoURL || '-'; }; +export const subscriptionName = (source?: TableSource): string | undefined => + !source || source.type === 'other' ? undefined : source.subscriptionName; + export const versionLabel = (source: TableSource): string => { switch (source.type) { case 'git': diff --git a/ui/src/features/project/pipelines/warehouse/repo-subscriptions.tsx b/ui/src/features/project/pipelines/warehouse/repo-subscriptions.tsx index cef27e5e70..cdff3ef74f 100644 --- a/ui/src/features/project/pipelines/warehouse/repo-subscriptions.tsx +++ b/ui/src/features/project/pipelines/warehouse/repo-subscriptions.tsx @@ -29,6 +29,12 @@ export const RepoSubscriptions = ({ subscriptions }: Props) => { column={1} style={{ width: '40%', minWidth: 500 }} > + {!!subscription.name && ( + + {subscription.name} + + )} + { column={1} style={{ width: '40%', minWidth: 500 }} > + {!!subscription.name && ( + + {subscription.name} + + )} + {subscription.git?.repoURL} @@ -110,6 +122,12 @@ export const RepoSubscriptions = ({ subscriptions }: Props) => { column={1} style={{ width: '40%', minWidth: 500 }} > + {!!subscription.name && ( + + {subscription.name} + + )} + { column={1} style={{ width: '40%', minWidth: 500 }} > - {subscription.name} + {!!subscription.name && ( + {subscription.name} + )} + {subscription.subscription.subscriptionType} diff --git a/ui/src/features/stage/create-warehouse/create-warehouse-wizard.tsx b/ui/src/features/stage/create-warehouse/create-warehouse-wizard.tsx index 419d427516..a046198177 100644 --- a/ui/src/features/stage/create-warehouse/create-warehouse-wizard.tsx +++ b/ui/src/features/stage/create-warehouse/create-warehouse-wizard.tsx @@ -93,15 +93,15 @@ export const CreateWarehouseWizard = (props: CreateWarehouseWizardProps) => { + onChange={(subscriptions) => { setFormState({ ...formState, spec: { ...(formState?.spec || {}), subscriptions } - }) - } + }); + }} /> diff --git a/ui/src/features/stage/create-warehouse/subscription-wizard.tsx b/ui/src/features/stage/create-warehouse/subscription-wizard.tsx index e546a481b3..7404b5723c 100644 --- a/ui/src/features/stage/create-warehouse/subscription-wizard.tsx +++ b/ui/src/features/stage/create-warehouse/subscription-wizard.tsx @@ -3,7 +3,7 @@ import { faEye, faTrash, IconDefinition } from '@fortawesome/free-solid-svg-icon import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Form from '@rjsf/antd'; import validator from '@rjsf/validator-ajv8'; -import { Button, Card, Collapse, Modal, Select, Tag, Typography } from 'antd'; +import { Button, Card, Collapse, Input, Modal, Select, Tag, Typography } from 'antd'; import AntdFormLabel from 'antd/es/form/FormItemLabel'; import classNames from 'classnames'; import { JSONSchema7 } from 'json-schema'; @@ -15,6 +15,7 @@ import { FieldTemplate } from '@ui/features/common/form/rjsf/field-template'; import { ObjectFieldTemplate } from '@ui/features/common/form/rjsf/object-field-template'; import { IconSetByKargoTerminology } from '@ui/features/common/icons'; import { ObjectDescription } from '@ui/features/common/object-description'; +import { SubscriptionName } from '@ui/features/common/subscription-name'; import { warehouseCreateFormJSONSchema } from './schema'; @@ -31,6 +32,8 @@ export const SubscriptionWizard = (props: { subscriptionTypes[2] /* image as default and common subscription */ ); + const [name, setName] = useState(''); + return ( <> @@ -61,6 +64,16 @@ export const SubscriptionWizard = (props: { value={selectedNewSubscription} onChange={(newSubscription) => setSelectedNewSubscription(newSubscription)} /> +
+ + setName(e.target.value)} + placeholder='Optional' + /> +
+ onSubmit={(data) => { props.onChange([ ...props.subscriptions, { + ...(name ? { name } : {}), [selectedNewSubscription]: data.formData } - ]) - } + ]); + setName(''); + }} >