-
Story
-
Resolution: Unresolved
-
Undefined
-
None
-
None
-
None
[UI] Add Sparse Manifest Visualization
Summary
Add visual indicators in tag/manifest views showing when a manifest list is sparse (not all child manifests are present locally). Differentiate between locally-present and missing architecture entries in manifest list display to help users understand which architectures are available.
Acceptance Criteria
- [ ] Manifest list view shows which child manifests are present vs missing
- [ ] Sparse manifest lists are clearly labeled/indicated
- [ ] Architecture entries show availability status (present/missing)
- [ ] Missing architecture entries are visually distinct (grayed out, icon, etc.)
- [ ] Tooltip or help text explains what "sparse" means
- [ ] Tag list indicates when a tag points to a sparse manifest list
- [ ] UI gracefully handles non-sparse (complete) manifest lists
- [ ] UI follows PatternFly design patterns
Technical Requirements
API Data Requirements
The manifest details API needs to return information about which child manifests exist locally. This may require backend changes or can leverage existing data:
Expected API Response Structure:
{
"digest": "sha256:abc...",
"media_type": "application/vnd.oci.image.index.v1+json",
"is_sparse": true,
"manifests": [
{
"digest": "sha256:123...",
"architecture": "amd64",
"os": "linux",
"is_present": true
},
{
"digest": "sha256:456...",
"architecture": "arm64",
"os": "linux",
"is_present": false
}
]
}
Tag List Enhancement
File: web/src/routes/RepositoryDetails/Tags/ (or similar)
Add sparse indicator to tag list:
interface TagRowProps { tag: string; digest: string; isSparse?: boolean; // ... other props } const TagRow: React.FC<TagRowProps> = ({ tag, digest, isSparse }) => { return ( <Tr> <Td>{tag}</Td> <Td> {digest} {isSparse && ( <Tooltip content="This manifest list is sparse - not all architectures are present locally"> <Label color="orange" icon={<InfoCircleIcon />}> Sparse </Label> </Tooltip> )} </Td> {/* ... other columns */} </Tr> ); };
Manifest Details View Enhancement
File: web/src/routes/TagDetails/ (or manifest details component)
Create architecture presence visualization:
import React from 'react'; import { Card, CardBody, CardTitle, Label, LabelGroup, Tooltip, Icon, } from '@patternfly/react-core'; import { CheckCircleIcon, ExclamationTriangleIcon, } from '@patternfly/react-icons'; interface ManifestEntry { digest: string; architecture: string; os: string; isPresent: boolean; size?: number; } interface ManifestListViewProps { manifests: ManifestEntry[]; isSparse: boolean; } export const ManifestListView: React.FC<ManifestListViewProps> = ({ manifests, isSparse, }) => { const presentCount = manifests.filter(m => m.isPresent).length; const totalCount = manifests.length; return ( <Card> <CardTitle> Architectures {isSparse && ( <Label color="orange" className="pf-u-ml-md"> Sparse ({presentCount}/{totalCount} present) </Label> )} </CardTitle> <CardBody> <LabelGroup> {manifests.map(manifest => ( <ArchitectureLabel key={manifest.digest} manifest={manifest} /> ))} </LabelGroup> </CardBody> </Card> ); }; const ArchitectureLabel: React.FC<{ manifest: ManifestEntry }> = ({ manifest }) => { if (manifest.isPresent) { return ( <Tooltip content={`Digest: ${manifest.digest}`}> <Label color="green" icon={<CheckCircleIcon />}> {manifest.architecture}/{manifest.os} </Label> </Tooltip> ); } return ( <Tooltip content={`Not present locally. Digest: ${manifest.digest}`}> <Label color="grey" icon={<ExclamationTriangleIcon />}> {manifest.architecture}/{manifest.os} (missing) </Label> </Tooltip> ); };
Sparse Manifest Banner
Add an alert banner when viewing a sparse manifest:
import { Alert, AlertActionLink } from '@patternfly/react-core'; const SparseManifestAlert: React.FC<{ missingArchs: string[] }> = ({ missingArchs }) => { return ( <Alert variant="warning" title="Sparse Manifest" actionLinks={ <AlertActionLink>Learn more</AlertActionLink> } > This manifest list is sparse. The following architectures are not present locally:{' '} {missingArchs.join(', ')}. Pulling these architectures will result in an error. </Alert> ); };
Resource Updates
File: web/src/resources/TagResource.ts (or similar)
Update resource types to include sparse information:
export interface ManifestDetails { digest: string; mediaType: string; isSparse: boolean; manifests?: ManifestChild[]; } export interface ManifestChild { digest: string; architecture: string; os: string; isPresent: boolean; size: number; }
Implementation Notes
Existing Patterns to Follow
- PatternFly Labels: Use @patternfly/react-core Label component
- Tooltips: Use PatternFly Tooltip for additional information
- Alerts: Use PatternFly Alert for warnings
- Frontend conventions: See web/AGENTS.md for React patterns
Visual Design
- Present architectures: Green label with checkmark icon
- Missing architectures: Grey/muted label with warning icon
- Sparse indicator: Orange "Sparse" label in tag list
- Count display: "3/5 present" style for quick overview
UI States
- Complete manifest list: No sparse indicator, all architectures shown normally
- Sparse manifest list: Sparse label, mixed present/missing architectures
- Single architecture manifest: No architecture section shown
- Loading: Skeleton/spinner while loading manifest details
Accessibility
- Use proper color contrast for labels
- Icons should have aria-labels
- Tooltips should be keyboard accessible
- Screen readers should understand present/missing status
Backend Considerations
If the current manifest API doesn't return isPresent information for child manifests, a backend change may be needed:
File: endpoints/api/manifest.py (or relevant API)
def get_manifest_children(manifest):
"""Get child manifests with presence information."""
children = []
for child_ref in manifest.child_manifests:
child_manifest = lookup_manifest(child_ref.digest)
children.append({
"digest": child_ref.digest,
"architecture": child_ref.platform.architecture,
"os": child_ref.platform.os,
"is_present": child_manifest is not None,
"size": child_ref.size,
})
return children
Dependencies
- Story 02: Registry core sparse manifest support
- Backend API may need updates to return child manifest presence information
Testing Requirements
Component Tests
File: web/src/routes/TagDetails/__tests__/ManifestListView.test.tsx (new file)
describe('ManifestListView', () => { it('renders complete manifest list without sparse indicator', () => { const manifests = [ { digest: 'sha256:1', architecture: 'amd64', os: 'linux', isPresent: true }, { digest: 'sha256:2', architecture: 'arm64', os: 'linux', isPresent: true }, ]; render(<ManifestListView manifests={manifests} isSparse={false} />); expect(screen.queryByText('Sparse')).not.toBeInTheDocument(); }); it('renders sparse manifest list with indicator', () => { const manifests = [ { digest: 'sha256:1', architecture: 'amd64', os: 'linux', isPresent: true }, { digest: 'sha256:2', architecture: 'arm64', os: 'linux', isPresent: false }, ]; render(<ManifestListView manifests={manifests} isSparse={true} />); expect(screen.getByText(/Sparse/)).toBeInTheDocument(); expect(screen.getByText(/1\/2 present/)).toBeInTheDocument(); }); it('shows present architecture with green label', () => { // Verify green color/icon for present }); it('shows missing architecture with grey label', () => { // Verify grey color/warning icon for missing }); it('displays tooltip with digest on hover', () => { // Verify tooltip content }); }); describe('SparseManifestAlert', () => { it('displays warning with missing architectures', () => { render(<SparseManifestAlert missingArchs={['arm64', 'ppc64le']} />); expect(screen.getByText(/arm64/)).toBeInTheDocument(); expect(screen.getByText(/ppc64le/)).toBeInTheDocument(); }); });
Visual Tests
If using visual regression testing:
- Capture screenshots of complete vs sparse manifest views
- Verify color contrast and accessibility
Definition of Done
- [ ] Code implemented and follows project conventions
- [ ] All acceptance criteria met
- [ ] Component tests written and passing
- [ ] UI is accessible (color contrast, tooltips, screen readers)
- [ ] UI follows PatternFly design patterns
- [ ] Visual design reviewed and approved
- [ ] Code reviewed and approved