diff --git a/redisinsight/ui/src/constants/featureFlags.ts b/redisinsight/ui/src/constants/featureFlags.ts index b953774f15..e149f65548 100644 --- a/redisinsight/ui/src/constants/featureFlags.ts +++ b/redisinsight/ui/src/constants/featureFlags.ts @@ -7,4 +7,5 @@ export enum FeatureFlags { disabledByEnv = 'disabledByEnv', rdi = 'redisDataIntegration', hashFieldExpiration = 'hashFieldExpiration', + enhancedCloudUI = 'enhancedCloudUI', } diff --git a/redisinsight/ui/src/pages/home/HomePage.spec.tsx b/redisinsight/ui/src/pages/home/HomePage.spec.tsx index 0e61a57884..bbc6ded944 100644 --- a/redisinsight/ui/src/pages/home/HomePage.spec.tsx +++ b/redisinsight/ui/src/pages/home/HomePage.spec.tsx @@ -1,5 +1,6 @@ import React from 'react' import { render, screen } from 'uiSrc/utils/test-utils' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' import HomePage from './HomePage' jest.mock('uiSrc/slices/panels/sidePanels', () => ({ @@ -9,6 +10,24 @@ jest.mock('uiSrc/slices/panels/sidePanels', () => ({ }), })) +jest.mock('uiSrc/slices/content/create-redis-buttons', () => ({ + ...jest.requireActual('uiSrc/slices/content/create-redis-buttons'), + contentSelector: jest.fn().mockReturnValue({ + data: { + cloud_list_of_databases: {} + } + }), +})) + +jest.mock('uiSrc/slices/app/features', () => ({ + ...jest.requireActual('uiSrc/slices/app/features'), + appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({ + enhancedCloudUI: { + flag: false + } + }), +})) + /** * HomePage tests * @@ -30,4 +49,26 @@ describe('HomePage', () => { expect(screen.getByTestId('side-panels-insights')).toBeInTheDocument() }) + + it('should not render free cloud db with feature flag disabled', async () => { + (appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValueOnce({ + enhancedCloudUI: { + flag: false + } + }) + await render() + + expect(screen.queryByTestId('db-row_create-free-cloud-db')).not.toBeInTheDocument() + }) + + it('should render free cloud db with feature flag enabled', async () => { + (appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValueOnce({ + enhancedCloudUI: { + flag: true + } + }) + await render() + + expect(screen.getByTestId('db-row_create-free-cloud-db')).toBeInTheDocument() + }) }) diff --git a/redisinsight/ui/src/pages/home/HomePage.tsx b/redisinsight/ui/src/pages/home/HomePage.tsx index ed261c7a81..b16e00f5d5 100644 --- a/redisinsight/ui/src/pages/home/HomePage.tsx +++ b/redisinsight/ui/src/pages/home/HomePage.tsx @@ -8,7 +8,7 @@ import { useDispatch, useSelector } from 'react-redux' import { clusterSelector, resetDataRedisCluster, resetInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' import { Nullable, setTitle } from 'uiSrc/utils' import { HomePageTemplate } from 'uiSrc/templates' -import { BrowserStorageItem } from 'uiSrc/constants' +import { BrowserStorageItem, FeatureFlags } from 'uiSrc/constants' import { resetKeys } from 'uiSrc/slices/browser/keys' import { resetCliHelperSettings, resetCliSettingsAction } from 'uiSrc/slices/cli/cli-settings' import { resetRedisearchKeysData } from 'uiSrc/slices/browser/redisearch' @@ -41,6 +41,7 @@ import DatabasePanelDialog from './components/database-panel-dialog' import './styles.scss' import styles from './styles.module.scss' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' enum OpenDialogName { AddDatabase = 'add', @@ -58,6 +59,7 @@ const HomePage = () => { const { instance: sentinelInstance } = useSelector(sentinelSelector) const { action, dbConnection } = useSelector(appRedirectionSelector) const { data: createDbContent } = useSelector(contentSelector) + const { [FeatureFlags.enhancedCloudUI]: enhancedCloudUIFeature } = useSelector(appFeatureFlagsFeaturesSelector) const { loading, @@ -73,7 +75,7 @@ const HomePage = () => { const { contextInstanceId } = useSelector(appContextSelector) - const predefinedInstances = createDbContent?.cloud_list_of_databases ? [ + const predefinedInstances = enhancedCloudUIFeature?.flag && createDbContent?.cloud_list_of_databases ? [ { id: CREATE_CLOUD_DB_ID, ...createDbContent.cloud_list_of_databases } as Instance ] : [] const isInstanceExists = instances.length > 0 || predefinedInstances.length > 0 diff --git a/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.test.tsx b/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.test.tsx index 968a7bf216..a3f5e0b93c 100644 --- a/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.test.tsx +++ b/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.test.tsx @@ -120,7 +120,7 @@ describe('DatabasesListWrapper', () => { expect(store.getActions()).toEqual([ setSSOFlow(OAuthSocialAction.Create), - setSocialDialogState(OAuthSocialSource.ListOfDatabases) + setSocialDialogState(OAuthSocialSource.DatabaseConnectionList) ]) }) diff --git a/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx b/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx index f36f69fe5a..adb2554984 100644 --- a/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx @@ -2,7 +2,8 @@ import { Criteria, EuiButtonIcon, EuiIcon, - EuiLink, EuiResizeObserver, + EuiLink, + EuiResizeObserver, EuiTableFieldDataColumnType, EuiText, EuiTextColor, @@ -53,7 +54,7 @@ import { setSSOFlow } from 'uiSrc/slices/instances/cloud' import { setSocialDialogState } from 'uiSrc/slices/oauth/cloud' import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' import { getUtmExternalLink } from 'uiSrc/utils/links' -import { CREATE_CLOUD_DB_ID } from 'uiSrc/pages/home/constants' +import { CREATE_CLOUD_DB_ID, HELP_LINKS } from 'uiSrc/pages/home/constants' import styles from './styles.module.scss' export interface Props { @@ -243,10 +244,19 @@ const DatabasesListWrapper = (props: Props) => { const handleClickFreeDb = () => { if (cloudSsoFeature?.flag) { dispatch(setSSOFlow(OAuthSocialAction.Create)) - dispatch(setSocialDialogState(OAuthSocialSource.ListOfDatabases)) + dispatch(setSocialDialogState(OAuthSocialSource.DatabaseConnectionList)) + sendEventTelemetry({ + event: TelemetryEvent.CLOUD_FREE_DATABASE_CLICKED, + eventData: { source: OAuthSocialSource.DatabaseConnectionList }, + }) return } + sendEventTelemetry({ + event: HELP_LINKS.cloud.event, + eventData: { source: HELP_LINKS.cloud.sources.databaseConnectionList }, + }) + const link = document.createElement('a') link.setAttribute('href', getUtmExternalLink(EXTERNAL_LINKS.tryFree, { campaign: 'list_of_databases' })) link.setAttribute('target', '_blank') diff --git a/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.spec.tsx b/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.spec.tsx index 081e1d506d..3bb3b81ea1 100644 --- a/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.spec.tsx @@ -1,10 +1,38 @@ import React from 'react' import { instance, mock } from 'ts-mockito' -import { render } from 'uiSrc/utils/test-utils' +import { render, screen } from 'uiSrc/utils/test-utils' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' import DatabaseListHeader, { Props } from './DatabaseListHeader' const mockedProps = mock() +jest.mock('uiSrc/slices/app/features', () => ({ + ...jest.requireActual('uiSrc/slices/app/features'), + appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({ + enhancedCloudUI: { + flag: false + } + }), +})) + +jest.mock('uiSrc/slices/content/create-redis-buttons', () => ({ + ...jest.requireActual('uiSrc/slices/content/create-redis-buttons'), + contentSelector: jest.fn().mockReturnValue({ + data: { + cloud: { + title: 'Try Redis Cloud: your ultimate Redis starting point', + description: 'Includes native support for JSON, Search and Query, and more', + links: { + main: { + altText: 'Try Redis Cloud.', + url: 'https://redis.io/try-free/?utm_source=redisinsight&utm_medium=main&utm_campaign=main' + } + }, + } + } + }), +})) + jest.mock('uiSrc/telemetry', () => ({ ...jest.requireActual('uiSrc/telemetry'), sendEventTelemetry: jest.fn(), @@ -14,4 +42,28 @@ describe('DatabaseListHeader', () => { it('should render', () => { expect(render()).toBeTruthy() }) + + it('should not show promo cloud button with disabled feature flag', () => { + (appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValueOnce({ + enhancedCloudUI: { + flag: true + } + }) + + render() + + expect(screen.queryByTestId('promo-btn')).not.toBeInTheDocument() + }) + + it('should show promo cloud button with enabled feature flag', () => { + (appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValueOnce({ + enhancedCloudUI: { + flag: false + } + }) + + render() + + expect(screen.getByTestId('promo-btn')).toBeInTheDocument() + }) }) diff --git a/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.tsx b/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.tsx index 180ce108cf..c61753701b 100644 --- a/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.tsx +++ b/redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useContext, useEffect, useState } from 'react' import { EuiButton, EuiFlexGroup, @@ -6,9 +6,22 @@ import { EuiSpacer, } from '@elastic/eui' import { useSelector } from 'react-redux' +import { isEmpty } from 'lodash' +import cx from 'classnames' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { instancesSelector } from 'uiSrc/slices/instances/instances' -import { OAuthSocialSource } from 'uiSrc/slices/interfaces' +import { OAuthSocialAction, OAuthSocialSource } from 'uiSrc/slices/interfaces' +import PromoLink from 'uiSrc/components/promo-link/PromoLink' + +import { OAuthSsoHandlerDialog } from 'uiSrc/components' +import { getPathToResource } from 'uiSrc/services/resourcesService' +import { ContentCreateRedis } from 'uiSrc/slices/interfaces/content' +import { HELP_LINKS } from 'uiSrc/pages/home/constants' +import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' +import { getContentByFeature } from 'uiSrc/utils/content' +import { ThemeContext } from 'uiSrc/contexts/themeContext' +import { FeatureFlags } from 'uiSrc/constants' import SearchDatabasesList from '../search-databases-list' import styles from './styles.module.scss' @@ -19,6 +32,24 @@ export interface Props { const DatabaseListHeader = ({ onAddInstance }: Props) => { const { data: instances } = useSelector(instancesSelector) + const featureFlags = useSelector(appFeatureFlagsFeaturesSelector) + const { loading, data } = useSelector(contentSelector) + + const [promoData, setPromoData] = useState() + + const { theme } = useContext(ThemeContext) + const { [FeatureFlags.enhancedCloudUI]: enhancedCloudUIFeature } = featureFlags + const isShowPromoBtn = !enhancedCloudUIFeature?.flag + + useEffect(() => { + if (loading || !data || isEmpty(data)) { + return + } + + if (data?.cloud && !isEmpty(data.cloud)) { + setPromoData(getContentByFeature(data.cloud, featureFlags)) + } + }, [loading, data, featureFlags]) const handleOnAddDatabase = () => { sendEventTelemetry({ @@ -30,25 +61,89 @@ const DatabaseListHeader = ({ onAddInstance }: Props) => { onAddInstance() } + const handleClickLink = (event: TelemetryEvent, eventData: any = {}) => { + if (event) { + sendEventTelemetry({ + event, + eventData: { + ...eventData + } + }) + } + } + + const handleCreateDatabaseClick = ( + event: TelemetryEvent, + eventData: any = {}, + ) => { + handleClickLink(event, eventData) + } + const AddInstanceBtn = () => ( - + DB + {!isShowPromoBtn ? (+ DB) : (+ Add Redis database)} + ) + const CreateBtn = ({ content }: { content: ContentCreateRedis }) => { + if (!isShowPromoBtn) return null + + const { title, description, styles: stylesCss, links } = content + // @ts-ignore + const linkStyles = stylesCss ? stylesCss[theme] : {} + return ( + + {(ssoCloudHandlerClick, isSSOEnabled) => ( + { + !isSSOEnabled && handleCreateDatabaseClick( + HELP_LINKS.cloud.event, + { source: HELP_LINKS.cloud.sources.databaseList }, + ) + ssoCloudHandlerClick(e, { source: OAuthSocialSource.ListOfDatabases, action: OAuthSocialAction.Create }) + }} + /> + )} + + ) + } + return (
+ {!loading && !isEmpty(data) && ( + + + {promoData && ( + + + + )} + + + )} {instances.length > 0 && ( diff --git a/redisinsight/ui/src/pages/home/components/database-list-header/styles.module.scss b/redisinsight/ui/src/pages/home/components/database-list-header/styles.module.scss index f65712a64f..8376968371 100644 --- a/redisinsight/ui/src/pages/home/components/database-list-header/styles.module.scss +++ b/redisinsight/ui/src/pages/home/components/database-list-header/styles.module.scss @@ -45,3 +45,28 @@ margin-left: auto !important; padding-left: 12px; } + +.promo { + display: flex !important; + @media only screen and (max-width: 800px) { + display: none !important; + } +} + +.cloudSsoPromoTooltip { + display: flex; + flex-direction: row; + line-height: normal; + font-size: 12px !important; +} +.cloudSsoPromoTooltipIcon { + width: 20px !important; + height: 20px !important; + margin-right: 8px; +} + +@include global.insights-open(1350px) { + .promo { + display: none !important; + } +} diff --git a/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss b/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss index 8f93499be7..8c6ec91a2e 100644 --- a/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss +++ b/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss @@ -89,6 +89,10 @@ border-color: var(--separatorColor) !important; } + .euiTextArea { + min-height: 80px; + } + .euiFormControlLayout--group { border-color: var(--separatorColor) !important; } diff --git a/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx b/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx index f4b9c8e868..d615174dff 100644 --- a/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx +++ b/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx @@ -99,7 +99,7 @@ const TlsDetails = (props: Props) => { value: cert.id, inputDisplay: cert.name, dropdownDisplay: ( -
+
{truncateText(cert.name, 25)}
{ value: `${cert.id}`, inputDisplay: cert.name, dropdownDisplay: ( -
+
{truncateText(cert.name, 25)}