From 0a4b6dccfc85bd890960700e9eba2aec34f6fd8c Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 20 Apr 2022 08:24:51 -0400 Subject: [PATCH] Feature Highlights: update upgrade components UI (#47885) (#47948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Highlights: add action prop * Highlight team sync for trial users * Add badges for trial highlights * Move events to UpgradeBox * Fix undefined license settings * Update snapshot * Update public/app/features/datasources/state/navModel.ts Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update public/app/features/datasources/state/navModel.ts Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update public/app/features/datasources/state/navModel.ts Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update copy and event handling Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> (cherry picked from commit 9c0aa09a85fb92e2177f2f9010b49e887c450e37) Co-authored-by: Alex Khomenko --- .../app/core/components/Upgrade/ProBadge.tsx | 7 +- .../core/components/Upgrade/UpgradeBox.tsx | 47 ++++++++++-- public/app/features/admin/utils.ts | 8 ++ .../features/datasources/state/navModel.ts | 29 ++++++-- public/app/features/teams/TeamGroupSync.tsx | 73 ++++++++++++++----- public/app/features/teams/TeamPages.tsx | 30 ++------ .../__snapshots__/TeamGroupSync.test.tsx.snap | 4 +- public/app/features/teams/state/navModel.ts | 8 +- 8 files changed, 144 insertions(+), 62 deletions(-) create mode 100644 public/app/features/admin/utils.ts diff --git a/public/app/core/components/Upgrade/ProBadge.tsx b/public/app/core/components/Upgrade/ProBadge.tsx index 27b92f9279f..1dfd81f8211 100644 --- a/public/app/core/components/Upgrade/ProBadge.tsx +++ b/public/app/core/components/Upgrade/ProBadge.tsx @@ -7,16 +7,17 @@ import { reportExperimentView } from '@grafana/runtime'; export interface Props extends HTMLAttributes { text?: string; experimentId?: string; + eventVariant?: string; } -export const ProBadge = ({ text = 'PRO', className, experimentId, ...htmlProps }: Props) => { +export const ProBadge = ({ text = 'PRO', className, experimentId, eventVariant = '', ...htmlProps }: Props) => { const styles = useStyles2(getStyles); useEffect(() => { if (experimentId) { - reportExperimentView(experimentId, 'test', ''); + reportExperimentView(experimentId, 'test', eventVariant); } - }, [experimentId]); + }, [experimentId, eventVariant]); return ( diff --git a/public/app/core/components/Upgrade/UpgradeBox.tsx b/public/app/core/components/Upgrade/UpgradeBox.tsx index 7b1efb2f929..aa712c8bf19 100644 --- a/public/app/core/components/Upgrade/UpgradeBox.tsx +++ b/public/app/core/components/Upgrade/UpgradeBox.tsx @@ -1,24 +1,41 @@ -import React, { HTMLAttributes } from 'react'; +import React, { HTMLAttributes, useEffect } from 'react'; import { css, cx } from '@emotion/css'; -import { Icon, LinkButton, useStyles2 } from '@grafana/ui'; +import { Button, Icon, LinkButton, useStyles2 } from '@grafana/ui'; import { GrafanaTheme2 } from '@grafana/data'; +import { reportExperimentView } from '@grafana/runtime/src'; type ComponentSize = 'sm' | 'md'; export interface Props extends HTMLAttributes { featureName: string; size?: ComponentSize; + text?: string; + eventVariant?: string; + featureId: string; } -export const UpgradeBox = ({ featureName, className, children, size = 'md', ...htmlProps }: Props) => { +export const UpgradeBox = ({ + featureName, + className, + children, + text, + featureId, + eventVariant = '', + size = 'md', + ...htmlProps +}: Props) => { const styles = useStyles2((theme) => getUpgradeBoxStyles(theme, size)); + useEffect(() => { + reportExperimentView(`feature-highlights-${featureId}`, 'test', eventVariant); + }, [eventVariant, featureId]); + return (

- You’ve discovered a Pro feature! Get the Grafana Pro plan to access {featureName}. + You’ve discovered a Pro feature! {text || `Get the Grafana Pro plan to access ${featureName}.`}

void; + }; } export const UpgradeContent = ({ @@ -101,6 +123,7 @@ export const UpgradeContent = ({ featureName, description, caption, + action, }: UpgradeContentProps) => { const styles = useStyles2(getUpgradeContentStyles); return ( @@ -115,6 +138,16 @@ export const UpgradeContent = ({ ))} + {action?.link && ( + + {action.text} + + )} + {action?.onClick && ( + + )} {featureUrl && ( Learn more @@ -146,6 +179,9 @@ const getUpgradeContentStyles = (theme: GrafanaTheme2) => { width: 100%; } `, + title: css` + color: ${theme.colors.text.maxContrast}; + `, description: css` color: ${theme.colors.text.primary}; font-weight: ${theme.typography.fontWeightLight}; @@ -168,9 +204,6 @@ const getUpgradeContentStyles = (theme: GrafanaTheme2) => { link: css` margin-left: ${theme.spacing(2)}; `, - title: css` - color: ${theme.colors.text.maxContrast}; - `, caption: css` font-weight: ${theme.typography.fontWeightLight}; margin: ${theme.spacing(1, 0, 0)}; diff --git a/public/app/features/admin/utils.ts b/public/app/features/admin/utils.ts new file mode 100644 index 00000000000..eea09437aa5 --- /dev/null +++ b/public/app/features/admin/utils.ts @@ -0,0 +1,8 @@ +import { config } from '@grafana/runtime/src'; + +export function isTrial() { + const settings = (config as any).licensing; + return settings?.isTrial; +} + +export const highlightTrial = () => isTrial() && config.featureToggles.featureHighlights; diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts index 24c82d75074..e9e215293cb 100644 --- a/public/app/features/datasources/state/navModel.ts +++ b/public/app/features/datasources/state/navModel.ts @@ -5,6 +5,7 @@ import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; import { ProBadge } from 'app/core/components/Upgrade/ProBadge'; import { GenericDataSourcePlugin } from '../settings/PluginSettings'; +import { highlightTrial } from '../../admin/utils'; const loadingDSType = 'Loading'; @@ -53,7 +54,8 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat const isLoadingNav = dataSource.type === loadingDSType; - const dsPermissions = { + const permissionsExperimentId = 'feature-highlights-data-source-permissions-badge'; + const dsPermissions: NavModelItem = { active: false, icon: 'lock', id: `datasource-permissions-${dataSource.uid}`, @@ -61,6 +63,10 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat url: `datasources/edit/${dataSource.uid}/permissions`, }; + if (highlightTrial() && !isLoadingNav) { + dsPermissions.tabSuffix = () => ProBadge({ experimentId: permissionsExperimentId, eventVariant: 'trial' }); + } + if (featureEnabled('dspermissions')) { if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) { navModel.children!.push(dsPermissions); @@ -69,11 +75,12 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat navModel.children!.push({ ...dsPermissions, url: dsPermissions.url + '/upgrade', - tabSuffix: () => ProBadge({ experimentId: 'feature-highlights-data-source-permissions-badge' }), + tabSuffix: () => ProBadge({ experimentId: permissionsExperimentId }), }); } - const analytics = { + const analyticsExperimentId = 'feature-highlights-data-source-insights-badge'; + const analytics: NavModelItem = { active: false, icon: 'info-circle', id: `datasource-insights-${dataSource.uid}`, @@ -81,17 +88,23 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat url: `datasources/edit/${dataSource.uid}/insights`, }; + if (highlightTrial() && !isLoadingNav) { + analytics.tabSuffix = () => ProBadge({ experimentId: analyticsExperimentId, eventVariant: 'trial' }); + } + if (featureEnabled('analytics')) { navModel.children!.push(analytics); } else if (highlightsEnabled && !isLoadingNav) { navModel.children!.push({ ...analytics, url: analytics.url + '/upgrade', - tabSuffix: () => ProBadge({ experimentId: 'feature-highlights-data-source-insights-badge' }), + tabSuffix: () => ProBadge({ experimentId: analyticsExperimentId }), }); } - const caching = { + const cachingExperimentId = 'feature-highlights-query-caching-badge'; + + const caching: NavModelItem = { active: false, icon: 'database', id: `datasource-cache-${dataSource.uid}`, @@ -100,13 +113,17 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat hideFromTabs: !pluginMeta.isBackend || !config.caching.enabled, }; + if (highlightTrial() && !isLoadingNav) { + caching.tabSuffix = () => ProBadge({ experimentId: cachingExperimentId, eventVariant: 'trial' }); + } + if (featureEnabled('caching')) { navModel.children!.push(caching); } else if (highlightsEnabled && !isLoadingNav) { navModel.children!.push({ ...caching, url: caching.url + '/upgrade', - tabSuffix: () => ProBadge({ experimentId: 'feature-highlights-query-caching-badge' }), + tabSuffix: () => ProBadge({ experimentId: cachingExperimentId }), }); } diff --git a/public/app/features/teams/TeamGroupSync.tsx b/public/app/features/teams/TeamGroupSync.tsx index a550b0cd9df..dc3d78e1048 100644 --- a/public/app/features/teams/TeamGroupSync.tsx +++ b/public/app/features/teams/TeamGroupSync.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; -import { LegacyForms, Tooltip, Icon, Button } from '@grafana/ui'; +import { LegacyForms, Tooltip, Icon, Button, useTheme2 } from '@grafana/ui'; const { Input } = LegacyForms; import { StoreState, TeamGroup } from '../../types'; @@ -10,6 +10,8 @@ import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions'; import { getTeamGroups } from './state/selectors'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import { UpgradeBox, UpgradeContent, UpgradeContentProps } from 'app/core/components/Upgrade/UpgradeBox'; +import { highlightTrial } from 'app/features/admin/utils'; function mapStateToProps(state: StoreState) { return { @@ -90,14 +92,25 @@ export class TeamGroupSync extends PureComponent { render() { const { isAdding, newGroupId } = this.state; const { groups, isReadOnly } = this.props; - return (
+ {highlightTrial() && ( + + )}
-

External group sync

- - - + {(!highlightTrial() || groups.length > 0) && ( + <> +

External group sync

+ + + + + )}
{groups.length > 0 && (
- {groups.length === 0 && !isAdding && ( - - )} + {groups.length === 0 && + !isAdding && + (highlightTrial() ? ( + + ) : ( + + ))} {groups.length > 0 && (
@@ -163,4 +180,22 @@ export class TeamGroupSync extends PureComponent { } } +export const TeamSyncUpgradeContent = ({ action }: { action?: UpgradeContentProps['action'] }) => { + const theme = useTheme2(); + return ( + + ); +}; export default connect(mapStateToProps, mapDispatchToProps)(TeamGroupSync); diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index 685f7d1b5ac..949ce8dfa89 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -7,7 +7,7 @@ import Page from 'app/core/components/Page/Page'; import TeamMembers from './TeamMembers'; import TeamPermissions from './TeamPermissions'; import TeamSettings from './TeamSettings'; -import TeamGroupSync from './TeamGroupSync'; +import TeamGroupSync, { TeamSyncUpgradeContent } from './TeamGroupSync'; import { AccessControlAction, StoreState } from 'app/types'; import { loadTeam, loadTeamMembers } from './state/actions'; import { getTeam, getTeamMembers, isSignedInUserTeamAdmin } from './state/selectors'; @@ -15,9 +15,9 @@ import { getTeamLoadingNav } from './state/navModel'; import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/services/context_srv'; import { NavModel } from '@grafana/data'; -import { featureEnabled, reportExperimentView } from '@grafana/runtime'; +import { featureEnabled } from '@grafana/runtime'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { UpgradeBox, UpgradeContent } from 'app/core/components/Upgrade/UpgradeBox'; +import { UpgradeBox } from 'app/core/components/Upgrade/UpgradeBox'; interface TeamPageRouteParams { id: string; @@ -84,13 +84,6 @@ export class TeamPages extends PureComponent { async componentDidMount() { await this.fetchTeam(); - - const { isSyncEnabled } = this.state; - const currentPage = this.getCurrentPage(); - - if (currentPage === PageTypes.GroupSync && !isSyncEnabled && config.featureToggles.featureHighlights) { - reportExperimentView('feature-highlights-team-sync', 'test', ''); - } } async fetchTeam() { @@ -141,7 +134,7 @@ export class TeamPages extends PureComponent { renderPage(isSignedInUserTeamAdmin: boolean): React.ReactNode { const { isSyncEnabled } = this.state; - const { members, team, theme } = this.props; + const { members, team } = this.props; const currentPage = this.getCurrentPage(); const canReadTeam = contextSrv.hasAccessInMetadata( @@ -177,19 +170,8 @@ export class TeamPages extends PureComponent { } else if (config.featureToggles.featureHighlights) { return ( <> - - + + ); } diff --git a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap index 3abd26f7778..d9926d2bc57 100644 --- a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap @@ -67,10 +67,10 @@ exports[`Render should render component 1`] = ` + ProBadge({ experimentId: isLoadingTeam ? '' : 'feature-highlights-team-sync-badge', eventVariant: 'trial' }); + } + // With both Legacy and FGAC the tab is protected being featureEnabled // While team is loading we leave the teamsync tab // With FGAC the External Group Sync tab is available when user has ActionTeamsPermissionsRead for this team