RepositoryList: Display managed resource count (#110916)

* RepositoryList: display managed resource count

* display partial managed alert

* Update public/app/features/provisioning/Wizard/hooks/useResourceStats.ts

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* Update public/locales/en-US/grafana.json

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>

* only count dashboard and folder

---------

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
Yunwen Zheng
2025-09-12 10:20:29 -04:00
committed by GitHub
co-authored by Alex Khomenko
parent 1b066c3565
commit fc2de49b88
3 changed files with 122 additions and 40 deletions
@@ -1,10 +1,12 @@
import { useState } from 'react';
import { t } from '@grafana/i18n';
import { EmptyState, FilterInput, Stack } from '@grafana/ui';
import { t, Trans } from '@grafana/i18n';
import { Alert, Box, EmptyState, FilterInput, Icon, Stack } from '@grafana/ui';
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
import { RepositoryCard } from '../Repository/RepositoryCard';
import { useResourceStats } from '../Wizard/hooks/useResourceStats';
import { useIsProvisionedInstance } from '../hooks/useIsProvisionedInstance';
import { checkSyncSettings } from '../utils/checkSyncSettings';
interface Props {
@@ -13,33 +15,72 @@ interface Props {
export function RepositoryList({ items }: Props) {
const [query, setQuery] = useState('');
const isProvisionedInstance = useIsProvisionedInstance();
const { resourceCount, managedCount, unmanagedCount } = useResourceStats(items[0].metadata?.name);
const filteredItems = items.filter((item) => item.metadata?.name?.includes(query));
const { instanceConnected } = checkSyncSettings(items);
return (
<Stack direction={'column'} gap={3}>
{!instanceConnected && (
<Stack gap={2}>
<FilterInput
placeholder={t('provisioning.folder-repository-list.placeholder-search', 'Search')}
value={query}
onChange={setQuery}
/>
const getResourceCountSection = () => {
if (isProvisionedInstance) {
return (
<Box marginBottom={2}>
<Stack alignItems="center">
<Icon name="check" color="green" />
<Trans i18nKey="provisioning.folder-repository-list.all-resources-managed" count={resourceCount}>
All {{ count: resourceCount }} resources are managed
</Trans>
</Stack>
</Box>
);
}
if (filteredItems.length) {
return (
<Stack>
<Alert title={''} severity="info">
<Trans
i18nKey="provisioning.folder-repository-list.partial-managed"
count={resourceCount}
values={{ managedCount }}
>
{{ managedCount }}/{{ count: resourceCount }} resources managed. {{ unmanagedCount }} resources
aren&apos;t managed as code yet.
</Trans>
</Alert>
</Stack>
)}
<Stack direction={'column'} gap={2}>
{filteredItems.length ? (
filteredItems.map((item) => <RepositoryCard key={item.metadata?.name} repository={item} />)
) : (
<EmptyState
variant="not-found"
message={t(
'provisioning.folder-repository-list.no-results-matching-your-query',
'No results matching your query'
)}
/>
);
}
return null;
};
return (
<>
{getResourceCountSection()}
<Stack direction={'column'} gap={3}>
{!instanceConnected && (
<Stack gap={2}>
<FilterInput
placeholder={t('provisioning.folder-repository-list.placeholder-search', 'Search')}
value={query}
onChange={setQuery}
/>
</Stack>
)}
<Stack direction={'column'} gap={2}>
{filteredItems.length ? (
filteredItems.map((item) => <RepositoryCard key={item.metadata?.name} repository={item} />)
) : (
<EmptyState
variant="not-found"
message={t(
'provisioning.folder-repository-list.no-results-matching-your-query',
'No results matching your query'
)}
/>
)}
</Stack>
</Stack>
</Stack>
</>
);
}
@@ -5,10 +5,52 @@ import { t } from '@grafana/i18n';
import {
GetRepositoryFilesApiResponse,
GetResourceStatsApiResponse,
ManagerStats,
ResourceCount,
useGetRepositoryFilesQuery,
useGetResourceStatsQuery,
} from 'app/api/clients/provisioning/v0alpha1';
function getManagedCount(managed?: ManagerStats[]) {
let totalCount = 0;
// Loop through each managed repository
managed?.forEach((manager) => {
// Loop through stats inside each manager and sum up the counts
manager.stats.forEach((stat) => {
if (stat.group === 'folder.grafana.app' || stat.group === 'dashboard.grafana.app') {
totalCount += stat.count;
}
});
});
return totalCount;
}
function getResourceCount(stats?: ResourceCount[]) {
let counts: string[] = [];
let resourceCount = 0;
stats?.forEach((stat) => {
switch (stat.group) {
case 'folders':
case 'folder.grafana.app':
resourceCount += stat.count;
counts.push(t('provisioning.bootstrap-step.folders-count', '{{count}} folder', { count: stat.count }));
break;
case 'dashboard.grafana.app':
resourceCount += stat.count;
counts.push(t('provisioning.bootstrap-step.dashboards-count', '{{count}} dashboard', { count: stat.count }));
break;
}
});
return {
counts,
resourceCount,
};
}
/**
* Calculates resource statistics from API responses
*/
@@ -22,22 +64,7 @@ function getResourceStats(files?: GetRepositoryFilesApiResponse, stats?: GetReso
return isSupportedFile(path);
}).length;
let counts: string[] = [];
let resourceCount = 0;
stats?.instance?.forEach((stat) => {
switch (stat.group) {
case 'folders':
case 'folder.grafana.app':
resourceCount += stat.count;
counts.push(t('provisioning.bootstrap-step.folders-count', '{{count}} folder', { count: stat.count }));
break;
case 'dashboard.grafana.app':
resourceCount += stat.count;
counts.push(t('provisioning.bootstrap-step.dashboards-count', '{{count}} dashboard', { count: stat.count }));
break;
}
});
const { counts, resourceCount } = getResourceCount(stats?.instance);
return {
fileCount,
@@ -60,6 +87,14 @@ export function useResourceStats(repoName?: string, isLegacyStorage?: boolean) {
[filesQuery.data, resourceStatsQuery.data]
);
const { managedCount, unmanagedCount } = useMemo(() => {
return {
// managed does not exist in response when first time connecting to a repo
managedCount: getManagedCount(resourceStatsQuery.data?.managed),
unmanagedCount: getResourceCount(resourceStatsQuery.data?.unmanaged).resourceCount,
};
}, [resourceStatsQuery.data]);
const requiresMigration = isLegacyStorage || resourceCount > 0;
const shouldSkipSync = !requiresMigration && resourceCount === 0 && fileCount === 0;
@@ -72,6 +107,8 @@ export function useResourceStats(repoName?: string, isLegacyStorage?: boolean) {
: t('provisioning.bootstrap-step.empty', 'Empty');
return {
managedCount,
unmanagedCount,
resourceCount,
resourceCountString: resourceCountDisplay,
fileCount,
+4
View File
@@ -11384,7 +11384,11 @@
"label-sync-interval": "Sync Interval (seconds)"
},
"folder-repository-list": {
"all-resources-managed_one": "All {{count}} resource is managed",
"all-resources-managed_other": "All {{count}} resources are managed",
"no-results-matching-your-query": "No results matching your query",
"partial-managed_one": "{{managedCount}}/{{count}} resources managed. {{unmanagedCount}} resources aren't managed as code yet.",
"partial-managed_other": "{{managedCount}}/{{count}} resources managed. {{unmanagedCount}} resources aren't managed as code yet.",
"placeholder-search": "Search"
},
"get-default-values": {