mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Centralize resource graph presentation
This commit is contained in:
@@ -5,6 +5,7 @@ import { buildInfrastructureResourceHref } from '@/routing/resourceLinks';
|
||||
import {
|
||||
formatResourceChangeHeadline,
|
||||
formatResourceChangeKind,
|
||||
sortResourceChangesByObservedAt,
|
||||
} from '@/utils/resourceChangePresentation';
|
||||
|
||||
interface ResourceChangeSummaryProps {
|
||||
@@ -18,19 +19,8 @@ interface ResourceChangeSummaryProps {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const sortRecentChanges = (changes: ResourceChange[]): ResourceChange[] =>
|
||||
[...changes].sort((left, right) => {
|
||||
const observedDelta =
|
||||
new Date(right.observedAt).getTime() - new Date(left.observedAt).getTime();
|
||||
if (observedDelta !== 0) {
|
||||
return observedDelta;
|
||||
}
|
||||
|
||||
return right.id.localeCompare(left.id);
|
||||
});
|
||||
|
||||
export const ResourceChangeSummary: Component<ResourceChangeSummaryProps> = (props) => {
|
||||
const sortedChanges = createMemo(() => sortRecentChanges(props.changes ?? []));
|
||||
const sortedChanges = createMemo(() => sortResourceChangesByObservedAt(props.changes ?? []));
|
||||
const maxChanges = () => props.maxChanges ?? sortedChanges().length;
|
||||
const visibleChanges = createMemo(() => sortedChanges().slice(0, maxChanges()));
|
||||
const hasChanges = () => visibleChanges().length > 0;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
formatResourceCorrelationHeadline,
|
||||
formatResourceCorrelationPattern,
|
||||
formatResourceCorrelationSummary,
|
||||
formatResourceGraphSummaryText,
|
||||
sortResourceCorrelations,
|
||||
} from '@/utils/resourceCorrelationPresentation';
|
||||
|
||||
@@ -22,12 +23,6 @@ interface ResourceGraphSummaryProps {
|
||||
maxCorrelations?: number;
|
||||
}
|
||||
|
||||
const formatPluralCount = (count: number, singular: string, plural: string): string =>
|
||||
`${count} ${count === 1 ? singular : plural}`;
|
||||
|
||||
const formatSummaryParts = (parts: Array<string | null | undefined>): string =>
|
||||
parts.filter((part): part is string => Boolean(part && part.trim())).join(' · ');
|
||||
|
||||
export const ResourceGraphSummary: Component<ResourceGraphSummaryProps> = (props) => {
|
||||
const className = () => props.class?.trim() ?? '';
|
||||
const correlations = createMemo(() => sortResourceCorrelations(props.correlations ?? []));
|
||||
@@ -37,20 +32,13 @@ export const ResourceGraphSummary: Component<ResourceGraphSummaryProps> = (props
|
||||
const maxCorrelations = () => props.maxCorrelations ?? 3;
|
||||
const hasContent = () =>
|
||||
dependencies().length > 0 || dependents().length > 0 || correlations().length > 0;
|
||||
|
||||
const summaryText = () =>
|
||||
props.summaryText?.trim() ||
|
||||
formatSummaryParts([
|
||||
dependencies().length > 0
|
||||
? formatPluralCount(dependencies().length, 'dependency', 'dependencies')
|
||||
: null,
|
||||
dependents().length > 0
|
||||
? formatPluralCount(dependents().length, 'dependent', 'dependents')
|
||||
: null,
|
||||
correlations().length > 0
|
||||
? formatPluralCount(correlations().length, 'correlation', 'correlations')
|
||||
: null,
|
||||
]);
|
||||
formatResourceGraphSummaryText({
|
||||
dependenciesCount: dependencies().length,
|
||||
dependentsCount: dependents().length,
|
||||
correlationsCount: correlations().length,
|
||||
summaryText: props.summaryText,
|
||||
});
|
||||
|
||||
return (
|
||||
<Show when={hasContent()}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getResourceChangeKindPresentation,
|
||||
getResourceChangeSourceAdapterPresentation,
|
||||
getResourceChangeSourceTypePresentation,
|
||||
sortResourceChangesByObservedAt,
|
||||
} from '@/utils/resourceChangePresentation';
|
||||
|
||||
describe('resourceChangePresentation utils', () => {
|
||||
@@ -52,4 +53,29 @@ describe('resourceChangePresentation utils', () => {
|
||||
plural: 'Proxmox adapters',
|
||||
});
|
||||
});
|
||||
|
||||
it('sorts recent changes canonically', () => {
|
||||
const sorted = sortResourceChangesByObservedAt([
|
||||
{
|
||||
id: 'change-b',
|
||||
resourceId: 'vm-42',
|
||||
kind: 'config_update',
|
||||
observedAt: '2026-03-18T12:00:00Z',
|
||||
} as never,
|
||||
{
|
||||
id: 'change-a',
|
||||
resourceId: 'vm-42',
|
||||
kind: 'config_update',
|
||||
observedAt: '2026-03-18T12:05:00Z',
|
||||
} as never,
|
||||
{
|
||||
id: 'change-c',
|
||||
resourceId: 'vm-42',
|
||||
kind: 'config_update',
|
||||
observedAt: '2026-03-18T12:05:00Z',
|
||||
} as never,
|
||||
]);
|
||||
|
||||
expect(sorted.map((change) => change.id)).toEqual(['change-c', 'change-a', 'change-b']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
formatResourceCorrelationHeadline,
|
||||
formatResourceCorrelationPattern,
|
||||
formatResourceCorrelationSummary,
|
||||
formatResourceGraphSummaryText,
|
||||
sortResourceCorrelations,
|
||||
} from '@/utils/resourceCorrelationPresentation';
|
||||
|
||||
@@ -85,4 +86,22 @@ describe('resourceCorrelationPresentation utils', () => {
|
||||
|
||||
expect(sorted.map((item) => item.source_id)).toEqual(['storage-3', 'storage-1', 'storage-2']);
|
||||
});
|
||||
|
||||
it('formats canonical graph summary text', () => {
|
||||
expect(
|
||||
formatResourceGraphSummaryText({
|
||||
dependenciesCount: 2,
|
||||
dependentsCount: 1,
|
||||
correlationsCount: 3,
|
||||
}),
|
||||
).toBe('2 dependencies · 1 dependent · 3 correlations');
|
||||
expect(
|
||||
formatResourceGraphSummaryText({
|
||||
dependenciesCount: 0,
|
||||
dependentsCount: 0,
|
||||
correlationsCount: 0,
|
||||
summaryText: 'custom summary',
|
||||
}),
|
||||
).toBe('custom summary');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -180,3 +180,17 @@ export function formatResourceChangeHeadline(change: ResourceChange): string {
|
||||
}
|
||||
return `${formatResourceChangeKind(change.kind)}: ${change.resourceId}`;
|
||||
}
|
||||
|
||||
export function sortResourceChangesByObservedAt(
|
||||
changes: readonly ResourceChange[],
|
||||
): ResourceChange[] {
|
||||
return [...changes].sort((left, right) => {
|
||||
const observedDelta =
|
||||
new Date(right.observedAt).getTime() - new Date(left.observedAt).getTime();
|
||||
if (observedDelta !== 0) {
|
||||
return observedDelta;
|
||||
}
|
||||
|
||||
return right.id.localeCompare(left.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,3 +94,31 @@ export function sortResourceCorrelations(
|
||||
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
||||
});
|
||||
}
|
||||
|
||||
const formatPluralCount = (count: number, singular: string, plural: string): string =>
|
||||
`${count} ${count === 1 ? singular : plural}`;
|
||||
|
||||
const formatSummaryParts = (parts: Array<string | null | undefined>): string =>
|
||||
parts.filter((part): part is string => Boolean(part && part.trim())).join(' · ');
|
||||
|
||||
export function formatResourceGraphSummaryText(options: {
|
||||
dependenciesCount: number;
|
||||
dependentsCount: number;
|
||||
correlationsCount: number;
|
||||
summaryText?: string | null;
|
||||
}): string {
|
||||
return (
|
||||
options.summaryText?.trim() ||
|
||||
formatSummaryParts([
|
||||
options.dependenciesCount > 0
|
||||
? formatPluralCount(options.dependenciesCount, 'dependency', 'dependencies')
|
||||
: null,
|
||||
options.dependentsCount > 0
|
||||
? formatPluralCount(options.dependentsCount, 'dependent', 'dependents')
|
||||
: null,
|
||||
options.correlationsCount > 0
|
||||
? formatPluralCount(options.correlationsCount, 'correlation', 'correlations')
|
||||
: null,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user