BootstrapStep: Enhancement resource manage card options (#110723)

* BootstrapStep: card reorganize
* ConnectRepositoryButton: Move to Provisioning page actions spot
This commit is contained in:
Yunwen Zheng 2025-09-09 12:23:06 -04:00 committed by GitHub
parent d5fca9a5fa
commit fc0db985c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 155 additions and 52 deletions

View File

@ -10,6 +10,7 @@ import { Page } from 'app/core/components/Page/Page';
import GettingStarted from './GettingStarted/GettingStarted';
import GettingStartedPage from './GettingStarted/GettingStartedPage';
import { ConnectRepositoryButton } from './Shared/ConnectRepositoryButton';
import { RepositoryList } from './Shared/RepositoryList';
import { InlineSecureValueWarning } from './components/InlineSecureValueWarning';
import { useRepositoryList } from './hooks/useRepositoryList';
@ -67,6 +68,7 @@ export default function HomePage() {
<Page
navId="provisioning"
subTitle={t('provisioning.home-page.subtitle', 'View and manage your configured repositories')}
actions={activeTab === TabSelection.Repositories && <ConnectRepositoryButton items={items} />}
>
<Page.Contents isLoading={isLoading}>
{settings.data?.legacyStorage && (

View File

@ -1,7 +1,7 @@
import { useNavigate } from 'react-router-dom-v5-compat';
import { Trans } from '@grafana/i18n';
import { Alert, Button, Dropdown, Icon, Menu, Stack } from '@grafana/ui';
import { t, Trans } from '@grafana/i18n';
import { Button, Dropdown, Icon, Menu, Stack } from '@grafana/ui';
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1/endpoints.gen';
@ -18,22 +18,7 @@ export function ConnectRepositoryButton({ items }: Props) {
const navigate = useNavigate();
const { data: frontendSettings } = useGetFrontendSettingsQuery();
if (state.instanceConnected) {
return null;
}
if (state.maxReposReached) {
return (
<Alert title="" severity="info">
<Trans
i18nKey="provisioning.connect-repository-button.repository-limit-info-alert"
values={{ count: state.repoCount }}
>
Repository limit reached ({'{{count}}'})
</Trans>
</Alert>
);
}
const isButtonDisabled = state.instanceConnected || state.maxReposReached;
const availableTypes = frontendSettings?.availableRepositoryTypes || DEFAULT_REPOSITORY_TYPES;
const { orderedConfigs } = getOrderedRepositoryConfigs(availableTypes);
@ -55,7 +40,15 @@ export function ConnectRepositoryButton({ items }: Props) {
</Menu>
}
>
<Button variant="primary">
<Button
variant="primary"
disabled={isButtonDisabled}
tooltip={getConfigureRepoTooltip({
instanceConnected: state.instanceConnected,
maxReposReached: state.maxReposReached,
count: state.repoCount,
})}
>
<Stack alignItems="center">
<Trans i18nKey="provisioning.connect-repository-button.configure">Configure</Trans>
<Icon name={'angle-down'} />
@ -64,3 +57,32 @@ export function ConnectRepositoryButton({ items }: Props) {
</Dropdown>
);
}
export function getConfigureRepoTooltip({
instanceConnected,
maxReposReached,
count,
}: {
instanceConnected: boolean;
maxReposReached: boolean;
count: number;
}) {
if (instanceConnected) {
return t(
'provisioning.connect-repository-button.instance-fully-managed-tooltip',
'Configuration is disabled because this instance is fully managed'
);
}
if (maxReposReached) {
return t(
'provisioning.connect-repository-button.repository-limit-reached-tooltip',
'Repository limit reached {{count}}',
{
count,
}
);
}
return '';
}

View File

@ -7,8 +7,6 @@ import { Repository } from 'app/api/clients/provisioning/v0alpha1';
import { RepositoryCard } from '../Repository/RepositoryCard';
import { checkSyncSettings } from '../utils/checkSyncSettings';
import { ConnectRepositoryButton } from './ConnectRepositoryButton';
interface Props {
items: Repository[];
}
@ -27,7 +25,6 @@ export function RepositoryList({ items }: Props) {
value={query}
onChange={setQuery}
/>
<ConnectRepositoryButton items={items} />
</Stack>
)}
<Stack direction={'column'} gap={2}>

View File

@ -140,9 +140,8 @@ describe('BootstrapStep', () => {
it('should render correct info for GitHub repository type', async () => {
setup();
expect(await screen.findByText('Grafana instance')).toBeInTheDocument();
expect(screen.getByText('External storage')).toBeInTheDocument();
expect(screen.getAllByText('Empty')).toHaveLength(2); // Both should show empty
expect(screen.getAllByText('External storage')).toHaveLength(2);
expect(screen.getAllByText('Empty')).toHaveLength(3); // Three elements should have the role "Empty" (2 external + 1 unmanaged)
});
it('should render correct info for local file repository type', async () => {
@ -170,7 +169,7 @@ describe('BootstrapStep', () => {
setup();
expect(await screen.findByText('2 files')).toBeInTheDocument();
expect(await screen.getAllByText('2 files')).toHaveLength(2);
});
it('should display resource counts when resources exist', async () => {

View File

@ -1,11 +1,15 @@
import { css } from '@emotion/css';
import { useEffect } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { Trans, t } from '@grafana/i18n';
import { Box, Card, Field, Input, LoadingPlaceholder, Stack, Text } from '@grafana/ui';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Box, Card, Field, Input, LoadingPlaceholder, Stack, Text, useStyles2 } from '@grafana/ui';
import { RepositoryViewList } from 'app/api/clients/provisioning/v0alpha1';
import { generateRepositoryTitle } from 'app/features/provisioning/utils/data';
import { BootstrapStepCardIcons } from './BootstrapStepCardIcons';
import { BootstrapStepResourceCounting } from './BootstrapStepResourceCounting';
import { useStepStatus } from './StepStatusContext';
import { useModeOptions } from './hooks/useModeOptions';
import { useResourceStats } from './hooks/useResourceStats';
@ -28,9 +32,11 @@ export function BootstrapStep({ settingsData, repoName }: Props) {
} = useFormContext<WizardFormData>();
const selectedTarget = watch('repository.sync.target');
const repositoryType = watch('repository.type');
const options = useModeOptions(repoName, settingsData);
const { target } = options[0];
const { resourceCountString, fileCountString, isLoading } = useResourceStats(repoName, settingsData?.legacyStorage);
const styles = useStyles2(getStyles);
useEffect(() => {
// Pick a name nice name based on type+settings
@ -60,25 +66,6 @@ export function BootstrapStep({ settingsData, repoName }: Props) {
return (
<Stack direction="column" gap={2}>
<Stack direction="column" gap={2}>
<Box alignItems="center" padding={4}>
<Stack direction="row" gap={4} alignItems="flex-start" justifyContent="center">
<Stack direction="column" gap={1} alignItems="center">
<Text color="secondary">
<Trans i18nKey="provisioning.bootstrap-step.grafana">Grafana instance</Trans>
</Text>
<Stack direction="row" gap={2}>
<Text variant="h4">{resourceCountString}</Text>
</Stack>
</Stack>
<Stack direction="column" gap={1} alignItems="center">
<Text color="secondary">
<Trans i18nKey="provisioning.bootstrap-step.ext-storage">External storage</Trans>
</Text>
<Text variant="h4">{fileCountString}</Text>
</Stack>
</Stack>
</Box>
<Controller
name="repository.sync.target"
control={control}
@ -94,12 +81,27 @@ export function BootstrapStep({ settingsData, repoName }: Props) {
noMargin
{...field}
>
<Card.Heading>{action.label}</Card.Heading>
<Card.Heading>
<Text variant="h5">{action.label}</Text>
</Card.Heading>
<Card.Description>
<div className={styles.divider} />
<Box paddingBottom={2}>
<BootstrapStepCardIcons target={action.target} repoType={repositoryType} />
</Box>
<Stack direction="column" gap={3}>
{action.description}
<Text color="primary">{action.subtitle}</Text>
</Stack>
<div className={styles.divider} />
<BootstrapStepResourceCounting
target={action.target}
fileCountString={fileCountString}
resourceCountString={resourceCountString}
/>
</Card.Description>
</Card>
))}
@ -138,3 +140,13 @@ export function BootstrapStep({ settingsData, repoName }: Props) {
</Stack>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
divider: css({
height: 1,
width: '100%',
backgroundColor: theme.colors.border.medium,
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
}),
});

View File

@ -0,0 +1,29 @@
import { Icon, Stack } from '@grafana/ui';
import { RepoIcon } from '../Shared/RepoIcon';
import { RepoType, Target } from './types';
export function BootstrapStepCardIcons({ target, repoType }: { target: Target; repoType: RepoType }) {
if (target === 'instance') {
return (
<Stack direction="row">
<Icon name="grafana" size="xxl" />
<Icon name="arrows-h" size="xxl" />
<RepoIcon type="github" />
</Stack>
);
}
if (target === 'folder') {
return (
<Stack>
<Icon name="folder" size="xxl" />
<Icon name="arrow-left" size="xxl" />
<RepoIcon type={repoType} />
</Stack>
);
}
return null;
}

View File

@ -0,0 +1,40 @@
import { Trans } from '@grafana/i18n';
import { Stack, Text } from '@grafana/ui';
import { Target } from './types';
export function BootstrapStepResourceCounting({
target,
fileCountString,
resourceCountString,
}: {
target: Target;
fileCountString: string;
resourceCountString: string;
}) {
if (target === 'instance') {
return (
<Stack direction="row" gap={3}>
<Stack gap={1}>
<Trans i18nKey="provisioning.bootstrap-step.external-storage-label">External storage</Trans>
<Text color="primary">{fileCountString}</Text>
</Stack>
<Stack gap={1}>
<Trans i18nKey="provisioning.bootstrap-step.unmanaged-resources-label">Unmanaged resources</Trans>{' '}
<Text color="primary">{resourceCountString}</Text>
</Stack>
</Stack>
);
}
if (target === 'folder') {
return (
<Stack gap={1}>
<Trans i18nKey="provisioning.bootstrap-step.external-storage-label">External storage</Trans>{' '}
<Text color="primary">{fileCountString}</Text>
</Stack>
);
}
return null;
}

View File

@ -11215,15 +11215,15 @@
"description-clear-repository-connection": "Add a clear name for this repository connection",
"empty": "Empty",
"error-field-required": "This field is required.",
"ext-storage": "External storage",
"external-storage-label": "External storage",
"files-count_one": "{{count}} files",
"files-count_other": "{{count}} files",
"folders-count_one": "{{count}} folder",
"folders-count_other": "{{count}} folder",
"grafana": "Grafana instance",
"label-display-name": "Display name",
"placeholder-my-repository-connection": "My repository connection",
"text-loading-resource-information": "Loading resource information..."
"text-loading-resource-information": "Loading resource information...",
"unmanaged-resources-label": "Unmanaged resources"
},
"check-repository": {
"check": "Check"
@ -11265,7 +11265,9 @@
},
"connect-repository-button": {
"configure": "Configure",
"repository-limit-info-alert": "Repository limit reached ({{count}})"
"instance-fully-managed-tooltip": "Configuration is disabled because this instance is fully managed",
"repository-limit-reached-tooltip_one": "Repository limit reached {{count}}",
"repository-limit-reached-tooltip_other": "Repository limit reached {{count}}"
},
"delete-repository-button": {
"button-delete": "Delete",