From 75ce8db0e26339273836d3f237078d76d8f78106 Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Tue, 25 Mar 2025 12:59:42 +0100 Subject: [PATCH] Provisioning: Align frontend more with designs (#102687) * feat: align frontend more with designs * feat: remove unnecessary fields * chore: remove imports * chore: move imports * fix: make label not spit out undefined * feat: update title of nav item * chore: make update-workspace * fix: don't wrap text with span unless required --------- Co-authored-by: Roberto Jimenez Sanchez --- .../grafana-ui/src/components/Forms/Field.tsx | 24 ++- pkg/services/navtree/navtreeimpl/admin.go | 2 +- .../GettingStarted/EnhancedFeatures.tsx | 101 +++++---- .../GettingStarted/FeatureCard.tsx | 12 +- .../GettingStarted/FeaturesList.tsx | 38 ++-- .../GettingStarted/GettingStartedPage.tsx | 2 +- .../Shared/ConnectRepositoryButton.tsx | 2 +- .../Shared/TokenPermissionsInfo.tsx | 57 +++-- .../provisioning/Wizard/BootstrapStep.tsx | 2 +- .../provisioning/Wizard/ConnectPage.tsx | 5 +- .../provisioning/Wizard/ConnectStep.tsx | 38 ++-- .../provisioning/Wizard/FinishStep.tsx | 195 +++++++++--------- .../Wizard/ProvisioningWizard.tsx | 2 +- .../features/provisioning/Wizard/actions.ts | 8 +- 14 files changed, 270 insertions(+), 218 deletions(-) diff --git a/packages/grafana-ui/src/components/Forms/Field.tsx b/packages/grafana-ui/src/components/Forms/Field.tsx index 10db5c83bfc..461159963c6 100644 --- a/packages/grafana-ui/src/components/Forms/Field.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.tsx @@ -15,6 +15,8 @@ export interface FieldProps extends HTMLAttributes { children: React.ReactElement; /** Label for the field */ label?: React.ReactNode; + /** Forcibly use a Label, despite passing a non-string node. */ + useLabel?: boolean; /** Description of the field */ description?: React.ReactNode; /** Indicates if field is in invalid state */ @@ -45,6 +47,7 @@ export const Field = React.forwardRef( ( { label, + useLabel, description, horizontal, invalid, @@ -63,14 +66,25 @@ export const Field = React.forwardRef( const styles = useStyles2(getFieldStyles); const inputId = htmlFor ?? getChildId(children); - const labelElement = - typeof label === 'string' ? ( + let labelElement: React.ReactNode; + if (typeof label === 'string') { + labelElement = ( - ) : ( - label ); + } else if (useLabel) { + labelElement = ( + + ); + } else { + labelElement = label; + } const childProps = deleteUndefinedProps({ invalid, disabled, loading }); return ( diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index c2a4ebd9406..f329d7a35b2 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -62,7 +62,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink } if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(ctx, featuremgmt.FlagProvisioning) { generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{ - Text: "Provisioning", + Text: "Remote provisioning", Id: "provisioning", SubTitle: "Manage resources from remote repositories", Url: s.cfg.AppSubURL + "/admin/provisioning", diff --git a/public/app/features/provisioning/GettingStarted/EnhancedFeatures.tsx b/public/app/features/provisioning/GettingStarted/EnhancedFeatures.tsx index 5b1f961345b..a772158998b 100644 --- a/public/app/features/provisioning/GettingStarted/EnhancedFeatures.tsx +++ b/public/app/features/provisioning/GettingStarted/EnhancedFeatures.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; -import { Box, Stack, Text, LinkButton, Icon, IconName } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Box, Stack, Text, LinkButton, Icon, IconName, useStyles2 } from '@grafana/ui'; import { FeatureCard } from './FeatureCard'; @@ -29,46 +30,60 @@ interface EnhancedFeaturesProps { onSetupPublicAccess: () => void; } -export const EnhancedFeatures = ({ hasPublicAccess, hasImageRenderer, onSetupPublicAccess }: EnhancedFeaturesProps) => ( - - Unlock enhanced functionality for GitHub - - - } - action={ - !hasPublicAccess && ( - - Set up public access - - ) - } - showBorder - /> - - - - - } - action={ - !hasImageRenderer && ( - - Set up image rendering - - ) - } - /> - +export const EnhancedFeatures = ({ hasPublicAccess, hasImageRenderer, onSetupPublicAccess }: EnhancedFeaturesProps) => { + const style = useStyles2(getStyles); + + return ( + + Unlock enhanced functionality for GitHub + + + } + action={ + !hasPublicAccess && ( + + Set up public access + + ) + } + /> + +
+ + + + + + } + action={ + !hasImageRenderer && ( + + Set up image rendering + + ) + } + /> + + - -); + ); +}; + +function getStyles(theme: GrafanaTheme2) { + return { + separator: css({ + borderRight: `2px solid ${theme.colors.border.weak}`, + }), + }; +} diff --git a/public/app/features/provisioning/GettingStarted/FeatureCard.tsx b/public/app/features/provisioning/GettingStarted/FeatureCard.tsx index 92db7ac7609..b70ad55d880 100644 --- a/public/app/features/provisioning/GettingStarted/FeatureCard.tsx +++ b/public/app/features/provisioning/GettingStarted/FeatureCard.tsx @@ -1,5 +1,3 @@ -import { css } from '@emotion/css'; - import { Box, Stack, Text } from '@grafana/ui'; interface FeatureCardProps { @@ -7,17 +5,11 @@ interface FeatureCardProps { description: string; icon?: React.ReactNode; action?: React.ReactNode; - showBorder?: boolean; } -export const FeatureCard = ({ title, description, icon, action, showBorder = false }: FeatureCardProps) => ( +export const FeatureCard = ({ title, description, icon, action }: FeatureCardProps) => ( -
+
{icon} {title} diff --git a/public/app/features/provisioning/GettingStarted/FeaturesList.tsx b/public/app/features/provisioning/GettingStarted/FeaturesList.tsx index 10e8fb90206..8e66c9b4f77 100644 --- a/public/app/features/provisioning/GettingStarted/FeaturesList.tsx +++ b/public/app/features/provisioning/GettingStarted/FeaturesList.tsx @@ -4,14 +4,18 @@ import { Repository } from 'app/api/clients/provisioning'; import { ConnectRepositoryButton } from '../Shared/ConnectRepositoryButton'; interface FeatureItemProps { - children: React.ReactNode; + children: NonNullable; } -const FeatureItem = ({ children }: FeatureItemProps) => ( - - {children} - -); +const FeatureItem = ({ children }: FeatureItemProps) => { + // We use a stack here to ensure the icon and text are aligned correctly. + return ( + + + {children} + + ); +}; interface FeaturesListProps { repos?: Repository[]; @@ -48,17 +52,12 @@ export const FeaturesList = ({ return ( - Provisioning as-code directly from Grafana + Manage your dashboards with remote provisioning + Manage dashboards as code and provision updates automatically - Manage your dashboards as code and deploy them automatically from your GitHub repository or local storage - - - Review, discuss, and approve dashboard changes with your team before they go live using GitHub pull requests - - - Export your existing dashboards as code and store them in GitHub repositories for version control and - collaboration + Store dashboards in version-controlled storage for better organization and history tracking + Migrate existing dashboards to storage for provisioning {hasPublicAccess && ( Automatically provision and update your dashboards as soon as changes are pushed to your GitHub repository @@ -68,9 +67,12 @@ export const FeaturesList = ({ Visual previews in pull requests to review your changes before going live )} - - Learn more - + {false && ( + // We haven't gotten the design for this quite yet. + + Learn more + + )} {actions()} diff --git a/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx b/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx index dd264fbcfd1..bbb0c40c885 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx @@ -11,7 +11,7 @@ export default function GettingStartedPage({ items }: Props) { - Maximum repos exist ({state.repoCount}) + Maximum repositories exist ({state.repoCount}) ); } diff --git a/public/app/features/provisioning/Shared/TokenPermissionsInfo.tsx b/public/app/features/provisioning/Shared/TokenPermissionsInfo.tsx index fda4ddfd057..84893e99186 100644 --- a/public/app/features/provisioning/Shared/TokenPermissionsInfo.tsx +++ b/public/app/features/provisioning/Shared/TokenPermissionsInfo.tsx @@ -1,36 +1,69 @@ -import { ControlledCollapse, TextLink } from '@grafana/ui'; +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { TextLink, useStyles2 } from '@grafana/ui'; export function TokenPermissionsInfo() { + const styles = useStyles2(getStyles); + return ( - +
- To create a new access token, navigate to{' '} + Go to{' '} - Personal access tokens - {' '} + GitHub Personal Access Tokens + + . Make sure to include these permissions under Repository:
-

Select the appropriate owner and repository. Then expand repository permissions, granting

- +
+ + + + - + - + - + - +
PermissionAccess
ContentContents Read and write
MetadataRead onlyRead-only
Pull requests  Pull requests Read and write
WebhooksRead and write   Read and write
- +
); } + +function getStyles(theme: GrafanaTheme2) { + return { + container: css({ + marginBottom: theme.spacing(1), + backgroundColor: theme.colors.background.secondary, + border: `1px solid ${theme.colors.border.weak}`, + position: 'relative', + borderRadius: theme.shape.radius.default, + width: '100%', + display: 'flex', + flexDirection: 'column', + flex: '1 1 0', + padding: theme.spacing(theme.components.panel.padding), + }), + permissionTable: css({ + tableLayout: 'auto', + width: '40%', + }), + headerSeparator: css({ + borderBottom: `1px solid ${theme.colors.border.weak}`, + }), + }; +} diff --git a/public/app/features/provisioning/Wizard/BootstrapStep.tsx b/public/app/features/provisioning/Wizard/BootstrapStep.tsx index 25cc2da9f1a..1b45cd6c632 100644 --- a/public/app/features/provisioning/Wizard/BootstrapStep.tsx +++ b/public/app/features/provisioning/Wizard/BootstrapStep.tsx @@ -132,7 +132,7 @@ export function BootstrapStep({ onOptionSelect, settingsData, repoName }: Props) <> {Boolean(state.resourceCount) && ( - Dashboards will be unavailable while running this process. + Dashboards will be unavailable while running this process )} {Boolean(state.fileCount) && Boolean(state.resourceCount) && ( diff --git a/public/app/features/provisioning/Wizard/ConnectPage.tsx b/public/app/features/provisioning/Wizard/ConnectPage.tsx index 0d3ba0270d5..02a43a975be 100644 --- a/public/app/features/provisioning/Wizard/ConnectPage.tsx +++ b/public/app/features/provisioning/Wizard/ConnectPage.tsx @@ -6,7 +6,10 @@ export default function ConnectPage() { return ( diff --git a/public/app/features/provisioning/Wizard/ConnectStep.tsx b/public/app/features/provisioning/Wizard/ConnectStep.tsx index 66dfb059ea0..e273d28cb54 100644 --- a/public/app/features/provisioning/Wizard/ConnectStep.tsx +++ b/public/app/features/provisioning/Wizard/ConnectStep.tsx @@ -29,25 +29,28 @@ export function ConnectStep() { return ( - { - const repoType = value?.value; - setValue('repository.type', repoType); - setValue( - 'repository.workflows', - getWorkflowOptions(repoType).map((v) => v.value) - ); - }} - /> + + { + const repoType = value?.value; + setValue('repository.type', repoType); + setValue( + 'repository.workflows', + getWorkflowOptions(repoType).map((v) => v.value) + ); + }} + /> + {isGithub && ( <> @@ -60,7 +63,7 @@ export function ConnectStep() { { setValue('repository.token', ''); @@ -73,21 +76,22 @@ export function ConnectStep() { diff --git a/public/app/features/provisioning/Wizard/FinishStep.tsx b/public/app/features/provisioning/Wizard/FinishStep.tsx index 10a4f133ecc..a4f8be2b8c5 100644 --- a/public/app/features/provisioning/Wizard/FinishStep.tsx +++ b/public/app/features/provisioning/Wizard/FinishStep.tsx @@ -1,22 +1,18 @@ +import { css } from '@emotion/css'; import { useEffect } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom-v5-compat'; -import { Alert, Field, FieldSet, Input, MultiCombobox, Stack, Switch, Text } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Field, Input, MultiCombobox, Stack, Switch, useStyles2 } from '@grafana/ui'; import { getWorkflowOptions } from '../Config/ConfigForm'; import { checkPublicAccess, checkImageRenderer } from '../GettingStarted/features'; -import { GETTING_STARTED_URL } from '../constants'; import { WizardFormData } from './types'; export function FinishStep() { - const { - register, - watch, - control, - formState: { errors }, - } = useFormContext(); + const { register, watch, control, formState } = useFormContext(); + const { errors } = formState; const type = watch('repository.type'); const isGithub = type === 'github'; @@ -24,7 +20,18 @@ export function FinishStep() { const hasImageRenderer = checkImageRenderer(); // Enable sync by default const { setValue } = useFormContext(); - const navigate = useNavigate(); + const style = useStyles2(getStyles); + + if (!isPublic || !hasImageRenderer) { + if (formState.defaultValues?.repository) { + formState.defaultValues.repository.generateDashboardPreviews = false; + } + } + if (!isPublic) { + if (formState.defaultValues?.repository) { + // TODO: Disable webhooks by default + } + } // Set sync enabled by default useEffect(() => { @@ -33,107 +40,89 @@ export function FinishStep() { return ( -
- {isGithub && isPublic && ( - - - Automatically provision and update your dashboards as soon as changes are pushed to your GitHub - repository. - - - )} - {isGithub && !isPublic && ( - Instructions} - onRemove={() => navigate(GETTING_STARTED_URL)} - > - Changes in git will eventually be pulled depending on the synchronization interval. - - )} - + {isGithub && ( + -
-
+ )} + + + ( + { + onChange(val.map((v) => v.value)); + }} + {...field} + /> + )} + /> + + + {isGithub && false /* TODO */ && ( - { - return ( - { - onChange(val.map((v) => v.value)); - }} - {...field} - /> - ); - }} - /> + {/* TODO: Make an option for the switch to control */} + - {isGithub && ( - <> - {isPublic ? ( - - Preview links will be automatically added to pull requests when changes are made. - - ) : ( - navigate(GETTING_STARTED_URL)} - buttonContent={Instructions} - > - Preview links in pull requests will not be available until a public URL is configured. - - )} + )} - {!hasImageRenderer && ( - navigate(GETTING_STARTED_URL)} - buttonContent={Instructions} - > - The image renderer is not configured. Preview images will not be available. - - )} - - {hasImageRenderer && isPublic && ( - <> - Render before/after images and link them to the pull request. - } - > - - - - This will render dashboards into an image that can be access by a public URL - - - )} - - )} -
+ {isGithub && ( + + Enable dashboard previews in pull requests{' '} + + (Requires image rendering.{' '} + + Set up image rendering + + ) + + + } + description="Adds an image preview of dashboard changes in pull requests. Images of your Grafana dashboards will be shared in your Git repository and visible to anyone with repository access." + disabled={!hasImageRenderer || !isPublic} + > + + + )}
); } + +function getStyles(theme: GrafanaTheme2) { + return { + explanation: css({ + color: theme.colors.text.disabled, + fontStyle: 'italic', + }), + explanationLink: css({ + color: theme.colors.text.link, + fontStyle: 'italic', + }), + }; +} diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx index a089dbcd2e9..68dff166ec3 100644 --- a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx +++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx @@ -12,7 +12,7 @@ import { WizardContent } from './WizardContent'; import { WizardFormData, WizardStep } from './types'; const steps: Array> = [ - { id: 'connection', name: 'Connect', title: 'Connect to repository', submitOnNext: true }, + { id: 'connection', name: 'Connect', title: 'Connect to external storage', submitOnNext: true }, { id: 'bootstrap', name: 'Bootstrap', title: 'Bootstrap repository', submitOnNext: true }, { id: 'migrate', name: 'Resources', title: 'Migrate resources', submitOnNext: false }, { id: 'pull', name: 'Resources', title: 'Pull resources', submitOnNext: false }, diff --git a/public/app/features/provisioning/Wizard/actions.ts b/public/app/features/provisioning/Wizard/actions.ts index b9abbad1585..aa1758c67cf 100644 --- a/public/app/features/provisioning/Wizard/actions.ts +++ b/public/app/features/provisioning/Wizard/actions.ts @@ -10,21 +10,21 @@ const migrateInstance: ModeOption = { target: 'instance', operation: 'migrate', label: 'Migrate instance to repository', - description: 'Save all Grafana resources to repository', + description: 'Save all Grafana resources in the repository', }; const pullInstance: ModeOption = { target: 'instance', operation: 'pull', label: 'Pull from repository to instance', - description: 'Pull resources from repository into this Grafana instance', + description: 'Pull resources from the repository into this Grafana instance', }; const pullFolder: ModeOption = { target: 'folder', operation: 'pull', label: 'Pull from repository to folder', - description: 'Pull repository resources into a specific folder', + description: 'Pull repository resources into a repository-managed Grafana folder', }; function getDisabledReason(action: ModeOption, resourceCount: number, folderConnected?: boolean) { @@ -80,7 +80,7 @@ export function getState( const state: SystemState = { resourceCount, - resourceCountString: counts.join(','), + resourceCountString: counts.join(',\n'), fileCount, actions: [], disabled: [],