Skip to content

Commit 6ca5cc6

Browse files
committed
#RI-4814 - Create a free Cloud database from triggers and function page
1 parent 0e1b3f1 commit 6ca5cc6

File tree

9 files changed

+377
-69
lines changed

9 files changed

+377
-69
lines changed

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

Lines changed: 164 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,134 @@
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'
67
import OAuthSelectPlan from './OAuthSelectPlan'
78

89
jest.mock('uiSrc/telemetry', () => ({
910
...jest.requireActual('uiSrc/telemetry'),
1011
sendEventTelemetry: jest.fn(),
1112
}))
1213

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',
14+
jest.mock('uiSrc/slices/oauth/cloud', () => {
15+
const defaultState = jest.requireActual('uiSrc/slices/oauth/cloud').initialState
16+
return {
17+
...jest.requireActual('uiSrc/slices/oauth/cloud'),
18+
oauthCloudSelector: jest.fn().mockReturnValue(defaultState),
19+
oauthCloudPlanSelector: jest.fn().mockReturnValue({
20+
isOpenDialog: true,
21+
data: [],
22+
})
23+
}
24+
})
25+
26+
jest.mock('uiSrc/slices/app/features', () => ({
27+
...jest.requireActual('uiSrc/slices/app/features'),
28+
appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({
29+
cloudSso: {
30+
flag: true,
31+
data: {
32+
selectPlan: {
33+
components: {
34+
triggersAndFunctions: [
35+
{
36+
provider: 'AWS',
37+
regions: ['ap-southeast-1']
38+
},
39+
{
40+
provider: 'GCP',
41+
regions: ['asia-northeast1']
42+
}
43+
]
44+
}
45+
}
2946
}
30-
}]
47+
},
3148
}),
3249
}))
3350

51+
const mockNoTFRegion = {
52+
id: 12148,
53+
type: 'fixed',
54+
name: 'Cache 30MB',
55+
provider: 'AWS',
56+
price: 0,
57+
region: 'eu-west-1',
58+
regionId: 4,
59+
details: {
60+
id: 4,
61+
name: 'eu-west-1',
62+
cloud: 'AWS',
63+
displayOrder: 4,
64+
countryName: 'Europe',
65+
cityName: 'Ireland',
66+
regionId: 4,
67+
flag: 'ie'
68+
}
69+
}
70+
71+
const mockRegions = [
72+
mockNoTFRegion,
73+
{
74+
id: 12150,
75+
type: 'fixed',
76+
name: 'Cache 30MB',
77+
provider: 'AWS',
78+
price: 0,
79+
region: 'ap-southeast-1',
80+
regionId: 5,
81+
details: {
82+
id: 5,
83+
name: 'ap-southeast-1',
84+
cloud: 'AWS',
85+
displayOrder: 7,
86+
countryName: 'Asia Pacific',
87+
cityName: 'Singapore',
88+
regionId: 5,
89+
flag: 'sg'
90+
}
91+
},
92+
{
93+
id: 12152,
94+
type: 'fixed',
95+
name: 'Cache 30MB',
96+
provider: 'Azure',
97+
price: 0,
98+
region: 'east-us',
99+
regionId: 16,
100+
details: {
101+
id: 16,
102+
name: 'east-us',
103+
cloud: 'Azure',
104+
displayOrder: 10,
105+
countryName: 'East US',
106+
cityName: 'Virginia',
107+
regionId: 16,
108+
flag: 'us'
109+
}
110+
},
111+
{
112+
id: 12153,
113+
type: 'fixed',
114+
name: 'Cache 30MB',
115+
provider: 'GCP',
116+
price: 0,
117+
region: 'us-central1',
118+
regionId: 27,
119+
details: {
120+
id: 27,
121+
name: 'us-central1',
122+
cloud: 'GCP',
123+
displayOrder: 17,
124+
countryName: 'North America',
125+
cityName: 'Iowa',
126+
regionId: 27,
127+
flag: 'us'
128+
}
129+
}
130+
]
131+
34132
let store: typeof mockedStore
35133
beforeEach(() => {
36134
cleanup()
@@ -39,6 +137,13 @@ beforeEach(() => {
39137
})
40138

41139
describe('OAuthSelectPlan', () => {
140+
beforeEach(() => {
141+
(oauthCloudPlanSelector as jest.Mock).mockReturnValue({
142+
isOpenDialog: true,
143+
data: mockRegions,
144+
})
145+
})
146+
42147
it('should render', () => {
43148
const { queryByTestId } = render(<OAuthSelectPlan />)
44149
expect(queryByTestId('oauth-select-plan-dialog')).toBeInTheDocument()
@@ -81,4 +186,44 @@ describe('OAuthSelectPlan', () => {
81186
]
82187
expect(store.getActions()).toEqual(expectedActions)
83188
})
189+
190+
it('if source is Trigger and Functions region with TF should be selected by default', async () => {
191+
(oauthCloudSelector as jest.Mock).mockReturnValue({
192+
...initialState,
193+
source: OAuthSocialSource.TriggersAndFunctions,
194+
})
195+
196+
const container = render(<OAuthSelectPlan />)
197+
198+
const { queryByTestId } = within(container.queryByTestId('select-oauth-region') as HTMLElement)
199+
const tfIconEl = queryByTestId(/tf-icon-/)
200+
201+
expect(tfIconEl).toBeInTheDocument()
202+
})
203+
204+
it('should display text if no Trigger and Function regions available on this vendor', async () => {
205+
(oauthCloudPlanSelector as jest.Mock).mockReturnValue({
206+
isOpenDialog: true,
207+
data: [mockNoTFRegion],
208+
})
209+
210+
const { queryByTestId } = render(<OAuthSelectPlan />)
211+
const selectDescriptionEl = queryByTestId('select-region-select-description')
212+
213+
expect(selectDescriptionEl).toBeInTheDocument()
214+
expect(selectDescriptionEl).toHaveTextContent('This vendor does not support triggers and functions capability.')
215+
})
216+
217+
it('should display text if regions is no available on this venodor', async () => {
218+
(oauthCloudPlanSelector as jest.Mock).mockReturnValue({
219+
isOpenDialog: true,
220+
data: [],
221+
})
222+
223+
const { queryByTestId } = render(<OAuthSelectPlan />)
224+
const selectDescriptionEl = queryByTestId('select-region-select-description')
225+
226+
expect(selectDescriptionEl).toBeInTheDocument()
227+
expect(selectDescriptionEl).toHaveTextContent('No regions available, try another vendor.')
228+
})
84229
})

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

Lines changed: 72 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))
5274

53-
setPlans(newPlans)
75+
const defaultPlan = filteredPlans.find(({ region }) => region === defaultRegion)
76+
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,27 +87,37 @@ 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
}
@@ -110,7 +146,7 @@ const OAuthSelectPlan = () => {
110146
<h2 className={styles.title}>Select cloud vendor</h2>
111147
</EuiTitle>
112148
<section className={styles.providers}>
113-
{OAuthProviders.map(({ icon, id, label }) => (
149+
{ OAuthProviders.map(({ icon, id, label }) => (
114150
<div className={styles.provider}>
115151
{id === providerSelected
116152
&& <div className={cx(styles.providerActiveIcon)}><EuiIcon type="check" /></div>}
@@ -121,7 +157,7 @@ const OAuthSelectPlan = () => {
121157
/>
122158
<EuiText className={styles.providerLabel}>{label}</EuiText>
123159
</div>
124-
))}
160+
)) }
125161
</section>
126162
<section className={styles.region}>
127163
<EuiText className={styles.regionLabel}>Region</EuiText>
@@ -136,6 +172,16 @@ const OAuthSelectPlan = () => {
136172
onChange={onChangeRegion}
137173
data-testid="select-oauth-region"
138174
/>
175+
{!!regionOptions.length && !plans.some(({ region = '' }) => tfProviderRegions?.includes(region)) && (
176+
<EuiText className={styles.selectDescription} data-testid="select-region-select-description">
177+
This vendor does not support triggers and functions capability.
178+
</EuiText>
179+
)}
180+
{!regionOptions.length && (
181+
<EuiText className={styles.selectDescription} data-testid="select-region-select-description">
182+
No regions available, try another vendor.
183+
</EuiText>
184+
)}
139185
</section>
140186
<footer className={styles.footer}>
141187
<EuiButton
@@ -156,7 +202,7 @@ const OAuthSelectPlan = () => {
156202
data-testid="submit-oauth-select-plan-dialog"
157203
aria-labelledby="submit oauth select plan dialog"
158204
>
159-
Create subscription
205+
Create database
160206
</EuiButton>
161207
</footer>
162208
</section>

0 commit comments

Comments
 (0)