Skip to content

Commit fbef054

Browse files
Merge pull request #2469 from RedisInsight/fe/feature/RI-4814-sso_from_triggers_and_functions
#RI-4814 - Create a free Cloud database from triggers and function page
2 parents 9e12ab0 + a37f575 commit fbef054

File tree

10 files changed

+380
-69
lines changed

10 files changed

+380
-69
lines changed

redisinsight/ui/src/components/oauth/oauth-select-plan/OAuthSelectPlan.spec.tsx

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,51 @@
11
import React from 'react'
22
import { cloneDeep } from 'lodash'
3-
import { cleanup, fireEvent, mockedStore, render } from 'uiSrc/utils/test-utils'
3+
import { cleanup, fireEvent, mockedStore, render, within } from 'uiSrc/utils/test-utils'
44
import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry'
5-
import { addFreeDb, oauthCloudPlanSelector } from 'uiSrc/slices/oauth/cloud'
5+
import { addFreeDb, oauthCloudPlanSelector, oauthCloudSelector, initialState } from 'uiSrc/slices/oauth/cloud'
6+
import { OAuthSocialSource } from 'uiSrc/slices/interfaces'
7+
import { MOCK_NO_TF_REGION, MOCK_REGIONS } from 'uiSrc/constants/mocks/mock-sso'
68
import OAuthSelectPlan from './OAuthSelectPlan'
79

810
jest.mock('uiSrc/telemetry', () => ({
911
...jest.requireActual('uiSrc/telemetry'),
1012
sendEventTelemetry: jest.fn(),
1113
}))
1214

13-
jest.mock('uiSrc/slices/oauth/cloud', () => ({
14-
...jest.requireActual('uiSrc/slices/oauth/cloud'),
15-
oauthCloudPlanSelector: jest.fn().mockReturnValue({
16-
isOpenDialog: true,
17-
data: [{
18-
id: 12148,
19-
type: 'fixed',
20-
name: 'Cache 30MB',
21-
provider: 'AWS',
22-
region: 'eu-west-1',
23-
price: 0,
24-
details: {
25-
countryName: 'Poland',
26-
cityName: 'Warsaw',
27-
id: 12148,
28-
region: 'eu-west-1',
15+
jest.mock('uiSrc/slices/oauth/cloud', () => {
16+
const defaultState = jest.requireActual('uiSrc/slices/oauth/cloud').initialState
17+
return {
18+
...jest.requireActual('uiSrc/slices/oauth/cloud'),
19+
oauthCloudSelector: jest.fn().mockReturnValue(defaultState),
20+
oauthCloudPlanSelector: jest.fn().mockReturnValue({
21+
isOpenDialog: true,
22+
data: [],
23+
})
24+
}
25+
})
26+
27+
jest.mock('uiSrc/slices/app/features', () => ({
28+
...jest.requireActual('uiSrc/slices/app/features'),
29+
appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({
30+
cloudSso: {
31+
flag: true,
32+
data: {
33+
selectPlan: {
34+
components: {
35+
triggersAndFunctions: [
36+
{
37+
provider: 'AWS',
38+
regions: ['ap-southeast-1']
39+
},
40+
{
41+
provider: 'GCP',
42+
regions: ['asia-northeast1']
43+
}
44+
]
45+
}
46+
}
2947
}
30-
}]
48+
},
3149
}),
3250
}))
3351

@@ -39,6 +57,13 @@ beforeEach(() => {
3957
})
4058

4159
describe('OAuthSelectPlan', () => {
60+
beforeEach(() => {
61+
(oauthCloudPlanSelector as jest.Mock).mockReturnValue({
62+
isOpenDialog: true,
63+
data: MOCK_REGIONS,
64+
})
65+
})
66+
4267
it('should render', () => {
4368
const { queryByTestId } = render(<OAuthSelectPlan />)
4469
expect(queryByTestId('oauth-select-plan-dialog')).toBeInTheDocument()
@@ -81,4 +106,44 @@ describe('OAuthSelectPlan', () => {
81106
]
82107
expect(store.getActions()).toEqual(expectedActions)
83108
})
109+
110+
it('if source is Trigger and Functions region with TF should be selected by default', async () => {
111+
(oauthCloudSelector as jest.Mock).mockReturnValue({
112+
...initialState,
113+
source: OAuthSocialSource.TriggersAndFunctions,
114+
})
115+
116+
const container = render(<OAuthSelectPlan />)
117+
118+
const { queryByTestId } = within(container.queryByTestId('select-oauth-region') as HTMLElement)
119+
const tfIconEl = queryByTestId(/tf-icon-/)
120+
121+
expect(tfIconEl).toBeInTheDocument()
122+
})
123+
124+
it('should display text if no Trigger and Function regions available on this vendor', async () => {
125+
(oauthCloudPlanSelector as jest.Mock).mockReturnValue({
126+
isOpenDialog: true,
127+
data: [MOCK_NO_TF_REGION],
128+
})
129+
130+
const { queryByTestId } = render(<OAuthSelectPlan />)
131+
const selectDescriptionEl = queryByTestId('select-region-select-description')
132+
133+
expect(selectDescriptionEl).toBeInTheDocument()
134+
expect(selectDescriptionEl).toHaveTextContent('This vendor does not support triggers and functions capability.')
135+
})
136+
137+
it('should display text if regions is no available on this venodor', async () => {
138+
(oauthCloudPlanSelector as jest.Mock).mockReturnValue({
139+
isOpenDialog: true,
140+
data: [],
141+
})
142+
143+
const { queryByTestId } = render(<OAuthSelectPlan />)
144+
const selectDescriptionEl = queryByTestId('select-region-select-description')
145+
146+
expect(selectDescriptionEl).toBeInTheDocument()
147+
expect(selectDescriptionEl).toHaveTextContent('No regions available, try another vendor.')
148+
})
84149
})

redisinsight/ui/src/components/oauth/oauth-select-plan/OAuthSelectPlan.tsx

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useEffect, useState } from 'react'
1+
import React, { useCallback, useContext, useEffect, useState } from 'react'
22
import {
33
EuiButton,
44
EuiIcon,
@@ -10,49 +10,75 @@ import {
1010
EuiTextColor,
1111
EuiTitle,
1212
} from '@elastic/eui'
13-
import { toNumber, filter } from 'lodash'
13+
import { toNumber, filter, get, find, first } from 'lodash'
1414
import { useDispatch, useSelector } from 'react-redux'
1515
import cx from 'classnames'
1616

1717
import {
1818
createFreeDbJob,
1919
oauthCloudPlanSelector,
20+
oauthCloudSelector,
2021
setIsOpenSelectPlanDialog,
22+
setSocialDialogState,
2123
} from 'uiSrc/slices/oauth/cloud'
2224
import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry'
2325
import { addInfiniteNotification } from 'uiSrc/slices/app/notifications'
2426
import { INFINITE_MESSAGES } from 'uiSrc/components/notifications/components'
2527
import { CloudJobStep } from 'uiSrc/electron/constants'
26-
28+
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
29+
import { FeatureFlags, Theme } from 'uiSrc/constants'
30+
import { OAuthSocialSource, TFRegion } from 'uiSrc/slices/interfaces'
31+
import { ThemeContext } from 'uiSrc/contexts/themeContext'
32+
import TriggeredFunctionsDarkSVG from 'uiSrc/assets/img/sidebar/gears.svg'
33+
import TriggeredFunctionsLightSVG from 'uiSrc/assets/img/sidebar/gears_active.svg'
34+
35+
import { CloudSubscriptionPlanResponse } from 'apiSrc/modules/cloud/subscription/dto'
2736
import { OAuthProvider, OAuthProviders } from './constants'
2837
import styles from './styles.module.scss'
2938

3039
export const DEFAULT_REGION = 'us-east-1'
3140
export const DEFAULT_PROVIDER = OAuthProvider.AWS
41+
const getTFProviderRegions = (regions: TFRegion[], provider: OAuthProvider) =>
42+
(find(regions, { provider }) || {}).regions || []
3243

3344
const OAuthSelectPlan = () => {
45+
const { theme } = useContext(ThemeContext)
3446
const { isOpenDialog, data: plansInit = [], loading } = useSelector(oauthCloudPlanSelector)
47+
const { source } = useSelector(oauthCloudSelector)
48+
const { [FeatureFlags.cloudSso]: cloudSsoFeature = {} } = useSelector(appFeatureFlagsFeaturesSelector)
49+
50+
const tfRegions: TFRegion[] = get(cloudSsoFeature, 'data.selectPlan.components.triggersAndFunctions', [])
3551

3652
const [plans, setPlans] = useState(plansInit || [])
3753
const [planIdSelected, setPlanIdSelected] = useState('')
3854
const [providerSelected, setProviderSelected] = useState<OAuthProvider>(DEFAULT_PROVIDER)
55+
const [tfProviderRegions, setTfProviderRegions] = useState(getTFProviderRegions(tfRegions, providerSelected))
3956

4057
const dispatch = useDispatch()
4158

59+
const isTFSource = source?.endsWith(OAuthSocialSource.TriggersAndFunctions)
60+
61+
useEffect(() => {
62+
setTfProviderRegions(getTFProviderRegions(tfRegions, providerSelected))
63+
}, [providerSelected])
64+
4265
useEffect(() => {
4366
if (!plansInit.length) {
4467
return
4568
}
4669

47-
const newPlans = filter(plansInit, { provider: providerSelected })
48-
.sort((a, b) => a?.details?.displayOrder - b?.details?.displayOrder)
49-
const planId = newPlans?.find(({ region }) => region === DEFAULT_REGION)?.id?.toString()
50-
|| newPlans[0]?.id?.toString()
51-
|| ''
70+
const defaultRegion = isTFSource ? first(tfProviderRegions) || DEFAULT_REGION : DEFAULT_REGION
71+
72+
const filteredPlans = filter(plansInit, { provider: providerSelected })
73+
.sort((a, b) => (a?.details?.displayOrder || 0) - (b?.details?.displayOrder || 0))
74+
75+
const defaultPlan = filteredPlans.find(({ region }) => region === defaultRegion)
5276

53-
setPlans(newPlans)
77+
const planId = (defaultPlan || first(filteredPlans) || {}).id?.toString() || ''
78+
79+
setPlans(filteredPlans)
5480
setPlanIdSelected(planId)
55-
}, [plansInit, providerSelected])
81+
}, [isTFSource, plansInit, providerSelected, tfProviderRegions])
5682

5783
const handleOnClose = useCallback(() => {
5884
sendEventTelemetry({
@@ -61,32 +87,45 @@ const OAuthSelectPlan = () => {
6187
setPlanIdSelected('')
6288
setProviderSelected(DEFAULT_PROVIDER)
6389
dispatch(setIsOpenSelectPlanDialog(false))
90+
dispatch(setSocialDialogState(null))
6491
}, [])
6592

6693
if (!isOpenDialog) return null
6794

95+
const getOptionDisplay = (item: CloudSubscriptionPlanResponse) => {
96+
const { region = '', details: { countryName = '', cityName = '' }, provider } = item
97+
const tfProviderRegions: string[] = find(tfRegions, { provider })?.regions || []
98+
99+
return (
100+
<EuiText color="subdued" size="s">
101+
{`${countryName} (${cityName})`}
102+
<EuiTextColor className={styles.regionName}>{region}</EuiTextColor>
103+
{ tfProviderRegions?.includes(region) && (
104+
<EuiIcon
105+
type={theme === Theme.Dark ? TriggeredFunctionsDarkSVG : TriggeredFunctionsLightSVG}
106+
className={styles.tfOptionIcon}
107+
data-testid={`tf-icon-${region}`}
108+
/>
109+
)}
110+
</EuiText>
111+
)
112+
}
113+
68114
const regionOptions: EuiSuperSelectOption<string>[] = plans.map(
69115
(item) => {
70-
const { id, region, details: { countryName = '', cityName = '' } } = item
116+
const { id, region = '' } = item
71117
return {
72118
value: `${id}`,
73-
inputDisplay: (
74-
<EuiText color="subdued" size="s">
75-
{`${countryName} (${cityName})`}
76-
<EuiTextColor className={styles.regionName}>{region}</EuiTextColor>
77-
</EuiText>
78-
),
79-
dropdownDisplay: (
80-
<EuiText color="subdued" size="s">
81-
{`${countryName} (${cityName})`}
82-
<EuiTextColor className={styles.regionName}>{region}</EuiTextColor>
83-
</EuiText>
84-
),
119+
inputDisplay: getOptionDisplay(item),
120+
dropdownDisplay: getOptionDisplay(item),
85121
'data-test-subj': `oauth-region-${region}`,
86122
}
87123
}
88124
)
89125

126+
const isVendorWithTFRegions = !!regionOptions.length
127+
&& !plans.some(({ region = '' }) => tfProviderRegions?.includes(region))
128+
90129
const onChangeRegion = (region: string) => {
91130
setPlanIdSelected(region)
92131
}
@@ -110,7 +149,7 @@ const OAuthSelectPlan = () => {
110149
<h2 className={styles.title}>Select cloud vendor</h2>
111150
</EuiTitle>
112151
<section className={styles.providers}>
113-
{OAuthProviders.map(({ icon, id, label }) => (
152+
{ OAuthProviders.map(({ icon, id, label }) => (
114153
<div className={styles.provider}>
115154
{id === providerSelected
116155
&& <div className={cx(styles.providerActiveIcon)}><EuiIcon type="check" /></div>}
@@ -121,7 +160,7 @@ const OAuthSelectPlan = () => {
121160
/>
122161
<EuiText className={styles.providerLabel}>{label}</EuiText>
123162
</div>
124-
))}
163+
)) }
125164
</section>
126165
<section className={styles.region}>
127166
<EuiText className={styles.regionLabel}>Region</EuiText>
@@ -136,6 +175,16 @@ const OAuthSelectPlan = () => {
136175
onChange={onChangeRegion}
137176
data-testid="select-oauth-region"
138177
/>
178+
{isVendorWithTFRegions && (
179+
<EuiText className={styles.selectDescription} data-testid="select-region-select-description">
180+
This vendor does not support triggers and functions capability.
181+
</EuiText>
182+
)}
183+
{!regionOptions.length && (
184+
<EuiText className={styles.selectDescription} data-testid="select-region-select-description">
185+
No regions available, try another vendor.
186+
</EuiText>
187+
)}
139188
</section>
140189
<footer className={styles.footer}>
141190
<EuiButton
@@ -156,7 +205,7 @@ const OAuthSelectPlan = () => {
156205
data-testid="submit-oauth-select-plan-dialog"
157206
aria-labelledby="submit oauth select plan dialog"
158207
>
159-
Create subscription
208+
Create database
160209
</EuiButton>
161210
</footer>
162211
</section>

redisinsight/ui/src/components/oauth/oauth-select-plan/styles.module.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@
8989
padding: 2px 45px;
9090
text-align: left;
9191

92+
.selectDescription {
93+
padding-top: 10px;
94+
color: var(--euiColorMediumShade) !important;
95+
}
96+
9297
:global(.euiSuperSelectControl) {
9398
border-radius: 4px;
9499
background-color: var(--euiColorLightestShade) !important;
@@ -108,6 +113,10 @@
108113
:global(.euiTextColor) {
109114
font-size: 14px !important;
110115
}
116+
117+
:global(.euiContextMenuItem__text) {
118+
position: relative;
119+
}
111120
}
112121

113122
.regionName {
@@ -125,3 +134,9 @@
125134
.button {
126135
margin-left: 8px;
127136
}
137+
138+
.tfOptionIcon {
139+
display: inline-block !important;
140+
width: 16px !important;
141+
margin-left: 11px;
142+
}

0 commit comments

Comments
 (0)