Skip to content

Commit a35dbcd

Browse files
Merge pull request #2596 from RedisInsight/fe/feature/RI-4936_import_existing_free_cloud_database
#RI-4936 - import existing free cloud database
2 parents 0cf9bf6 + 3a6c536 commit a35dbcd

File tree

14 files changed

+363
-27
lines changed

14 files changed

+363
-27
lines changed

redisinsight/api/src/modules/cloud/job/jobs/create-free-subscription-and-database.cloud-job.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CloudSubscriptionCapiService } from 'src/modules/cloud/subscription/clo
44
import { CreateFreeSubscriptionCloudJob } from 'src/modules/cloud/job/jobs/create-free-subscription.cloud-job';
55
import { CloudDatabaseCapiService } from 'src/modules/cloud/database/cloud-database.capi.service';
66
import { CloudJobName } from 'src/modules/cloud/job/constants';
7-
import { CloudJobStep } from 'src/modules/cloud/job/models';
7+
import { CloudJobStatus, CloudJobStep } from 'src/modules/cloud/job/models';
88
import { DatabaseService } from 'src/modules/database/database.service';
99
import { Database } from 'src/modules/database/models/database';
1010
import { CloudDatabaseAnalytics } from 'src/modules/cloud/database/cloud-database.analytics';
@@ -54,12 +54,18 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob {
5454

5555
this.changeState({ step: CloudJobStep.Database });
5656

57-
return this.runChildJob(
57+
const database = await this.runChildJob(
5858
CreateFreeDatabaseCloudJob,
5959
{
6060
subscriptionId: freeSubscription.id,
6161
},
6262
this.options,
6363
);
64+
65+
this.result = { resourceId: database.id };
66+
67+
this.changeState({ status: CloudJobStatus.Finished });
68+
69+
return database;
6470
}
6571
}

redisinsight/ui/src/components/global-subscriptions/CommonAppSubscription/CommonAppSubscription.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ const CommonAppSubscription = () => {
5353
})
5454

5555
socketRef.current.on(CloudJobEvents.Monitor, (data: CloudJobInfo) => {
56-
if ((data.name as unknown) === CloudJobName.CreateFreeDatabase) {
56+
const jobName = data.name as unknown
57+
if (
58+
jobName === CloudJobName.CreateFreeDatabase
59+
|| jobName === CloudJobName.CreateFreeSubscriptionAndDatabase
60+
|| jobName === CloudJobName.ImportFreeDatabase) {
5761
dispatch(setJob(data))
5862
}
5963
})

redisinsight/ui/src/components/notifications/Notifications.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import { IError, IMessage, InfiniteMessage } from 'uiSrc/slices/interfaces'
2222
import { ApiEncryptionErrors } from 'uiSrc/constants/apiErrors'
2323
import { DEFAULT_ERROR_MESSAGE } from 'uiSrc/utils'
2424
import { showOAuthProgress } from 'uiSrc/slices/oauth/cloud'
25-
2625
import { CustomErrorCodes } from 'uiSrc/constants'
26+
import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry'
27+
2728
import errorMessages from './error-messages'
2829
import { InfiniteMessagesIds } from './components'
2930

@@ -115,9 +116,25 @@ const Notifications = () => {
115116
text: Inner,
116117
color: 'success',
117118
onClose: () => {
118-
if (id === InfiniteMessagesIds.oAuthProgress) {
119-
dispatch(showOAuthProgress(false))
119+
switch (id) {
120+
case InfiniteMessagesIds.oAuthProgress:
121+
dispatch(showOAuthProgress(false))
122+
break
123+
case InfiniteMessagesIds.databaseExists:
124+
sendEventTelemetry({
125+
event: TelemetryEvent.CLOUD_IMPORT_EXISTING_DATABASE_FORM_CLOSED,
126+
})
127+
break
128+
case InfiniteMessagesIds.subscriptionExists:
129+
sendEventTelemetry({
130+
event: TelemetryEvent.CLOUD_CREATE_DATABASE_IN_SUBSCRIPTION_FORM_CLOSED,
131+
})
132+
break
133+
134+
default:
135+
break
120136
}
137+
121138
dispatch(removeInfiniteNotification(id))
122139
},
123140
toastLifeTimeMs: 3_600_000,

redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,70 @@ describe('INFINITE_MESSAGES', () => {
2424
})
2525
describe('PENDING_CREATE_DB', () => {
2626
it('should render message', () => {
27-
const { Inner } = INFINITE_MESSAGES.PENDING_CREATE_DB
27+
const { Inner } = INFINITE_MESSAGES.PENDING_CREATE_DB()
2828
expect(render(<>{Inner}</>)).toBeTruthy()
2929
})
3030
})
31+
describe('DATABASE_EXISTS', () => {
32+
it('should render message', () => {
33+
const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(jest.fn())
34+
expect(render(<>{Inner}</>)).toBeTruthy()
35+
})
36+
37+
it('should call onSuccess', () => {
38+
const onSuccess = jest.fn()
39+
const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess)
40+
render(<>{Inner}</>)
41+
42+
fireEvent.click(screen.getByTestId('import-db-sso-btn'))
43+
fireEvent.mouseUp(screen.getByTestId('database-exists-notification'))
44+
fireEvent.mouseDown(screen.getByTestId('database-exists-notification'))
45+
46+
expect(onSuccess).toBeCalled()
47+
})
48+
49+
it('should call onCancel', () => {
50+
const onSuccess = jest.fn()
51+
const onCancel = jest.fn()
52+
const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess, onCancel)
53+
render(<>{Inner}</>)
54+
55+
fireEvent.click(screen.getByTestId('cancel-import-db-sso-btn'))
56+
fireEvent.mouseUp(screen.getByTestId('database-exists-notification'))
57+
fireEvent.mouseDown(screen.getByTestId('database-exists-notification'))
58+
59+
expect(onCancel).toBeCalled()
60+
})
61+
})
62+
describe('SUBSCRIPTION_EXISTS', () => {
63+
it('should render message', () => {
64+
const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(jest.fn())
65+
expect(render(<>{Inner}</>)).toBeTruthy()
66+
})
67+
68+
it('should call onSuccess', () => {
69+
const onSuccess = jest.fn()
70+
const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(onSuccess)
71+
render(<>{Inner}</>)
72+
73+
fireEvent.click(screen.getByTestId('create-subscription-sso-btn'))
74+
fireEvent.mouseUp(screen.getByTestId('subscription-exists-notification'))
75+
fireEvent.mouseDown(screen.getByTestId('subscription-exists-notification'))
76+
77+
expect(onSuccess).toBeCalled()
78+
})
79+
80+
it('should call onCancel', () => {
81+
const onSuccess = jest.fn()
82+
const onCancel = jest.fn()
83+
const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(onSuccess, onCancel)
84+
render(<>{Inner}</>)
85+
86+
fireEvent.click(screen.getByTestId('cancel-create-subscription-sso-btn'))
87+
fireEvent.mouseUp(screen.getByTestId('subscription-exists-notification'))
88+
fireEvent.mouseDown(screen.getByTestId('subscription-exists-notification'))
89+
90+
expect(onCancel).toBeCalled()
91+
})
92+
})
3193
})

redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { CloudJobStep } from 'uiSrc/electron/constants'
55
export enum InfiniteMessagesIds {
66
oAuthProgress = 'oAuthProgress',
77
oAuthSuccess = 'oAuthSuccess',
8+
databaseExists = 'databaseExists',
9+
subscriptionExists = 'subscriptionExists',
810
}
911

1012
export const INFINITE_MESSAGES = {
11-
PENDING_CREATE_DB: (step: CloudJobStep) => ({
13+
PENDING_CREATE_DB: (step?: CloudJobStep) => ({
1214
id: InfiniteMessagesIds.oAuthProgress,
1315
Inner: (
1416
<div
@@ -25,6 +27,7 @@ export const INFINITE_MESSAGES = {
2527
{ (step === CloudJobStep.Credentials || !step) && 'Processing Cloud API keys…'}
2628
{ step === CloudJobStep.Subscription && 'Processing Cloud subscriptions…'}
2729
{ step === CloudJobStep.Database && 'Creating a free Cloud database…'}
30+
{ step === CloudJobStep.Import && 'Importing a free Cloud database…'}
2831
</span>
2932
</EuiTitle>
3033
<EuiText size="xs">
@@ -70,4 +73,84 @@ export const INFINITE_MESSAGES = {
7073
</div>
7174
)
7275
}),
76+
DATABASE_EXISTS: (onSuccess?: () => void, onClose?: () => void) => ({
77+
id: InfiniteMessagesIds.databaseExists,
78+
Inner: (
79+
<div
80+
role="presentation"
81+
onMouseDown={(e) => { e.preventDefault() }}
82+
onMouseUp={(e) => { e.preventDefault() }}
83+
data-testid="database-exists-notification"
84+
>
85+
<EuiTitle className="infiniteMessage__title"><span>You already have a free Redis Enterprise Cloud subscription.</span></EuiTitle>
86+
<EuiText size="xs">
87+
Do you want to import your existing database into RedisInsight?
88+
</EuiText>
89+
<EuiSpacer size="m" />
90+
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none">
91+
<EuiFlexItem grow={false}>
92+
<EuiButton
93+
fill
94+
size="s"
95+
color="secondary"
96+
onClick={() => onSuccess?.()}
97+
data-testid="import-db-sso-btn"
98+
>
99+
Import
100+
</EuiButton>
101+
</EuiFlexItem>
102+
<EuiFlexItem grow={false}>
103+
<EuiButton
104+
size="s"
105+
color="secondary"
106+
onClick={() => onClose?.()}
107+
data-testid="cancel-import-db-sso-btn"
108+
>
109+
Cancel
110+
</EuiButton>
111+
</EuiFlexItem>
112+
</EuiFlexGroup>
113+
</div>
114+
)
115+
}),
116+
SUBSCRIPTION_EXISTS: (onSuccess?: () => void, onClose?: () => void) => ({
117+
id: InfiniteMessagesIds.subscriptionExists,
118+
Inner: (
119+
<div
120+
role="presentation"
121+
onMouseDown={(e) => { e.preventDefault() }}
122+
onMouseUp={(e) => { e.preventDefault() }}
123+
data-testid="subscription-exists-notification"
124+
>
125+
<EuiTitle className="infiniteMessage__title"><span>Your subscription does not have a free Redis Enterprise Cloud database.</span></EuiTitle>
126+
<EuiText size="xs">
127+
Do you want to create a free database in your existing subscription?
128+
</EuiText>
129+
<EuiSpacer size="m" />
130+
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none">
131+
<EuiFlexItem grow={false}>
132+
<EuiButton
133+
fill
134+
size="s"
135+
color="secondary"
136+
onClick={() => onSuccess?.()}
137+
data-testid="create-subscription-sso-btn"
138+
>
139+
Create
140+
</EuiButton>
141+
</EuiFlexItem>
142+
<EuiFlexItem grow={false}>
143+
<EuiButton
144+
size="s"
145+
color="secondary"
146+
onClick={() => onClose?.()}
147+
data-testid="cancel-create-subscription-sso-btn"
148+
>
149+
Cancel
150+
</EuiButton>
151+
</EuiFlexItem>
152+
</EuiFlexGroup>
153+
</div>
154+
)
155+
}),
73156
}

redisinsight/ui/src/components/oauth/oauth-jobs/OAuthJobs.spec.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { addErrorNotification, addInfiniteNotification, removeInfiniteNotificati
99
import { RootState } from 'uiSrc/slices/store'
1010
import { loadInstances } from 'uiSrc/slices/instances/instances'
1111
import { INFINITE_MESSAGES, InfiniteMessagesIds } from 'uiSrc/components/notifications/components'
12+
import { CustomErrorCodes } from 'uiSrc/constants'
1213
import OAuthJobs from './OAuthJobs'
1314

1415
jest.mock('react-redux', () => ({
@@ -128,8 +129,68 @@ describe('OAuthJobs', () => {
128129
rerender(<OAuthJobs />)
129130

130131
const expectedActions = [
131-
removeInfiniteNotification(InfiniteMessagesIds.oAuthProgress),
132132
addErrorNotification({ response: { data: error } } as AxiosError),
133+
removeInfiniteNotification(InfiniteMessagesIds.oAuthProgress),
134+
]
135+
expect(clearStoreActions(store.getActions())).toEqual(
136+
clearStoreActions(expectedActions)
137+
)
138+
})
139+
140+
it('should call addInfiniteNotification and removeInfiniteNotification when errorCode is 11_108', async () => {
141+
const mockDatabaseId = '123'
142+
const error = {
143+
errorCode: CustomErrorCodes.CloudDatabaseAlreadyExistsFree,
144+
resource: {
145+
databaseId: mockDatabaseId
146+
}
147+
};
148+
(oauthCloudJobSelector as jest.Mock).mockImplementation(() => ({
149+
status: ''
150+
}))
151+
152+
const { rerender } = render(<OAuthJobs />);
153+
154+
(oauthCloudJobSelector as jest.Mock).mockImplementation(() => ({
155+
status: CloudJobStatus.Failed,
156+
error,
157+
}))
158+
159+
rerender(<OAuthJobs />)
160+
161+
const expectedActions = [
162+
addInfiniteNotification(INFINITE_MESSAGES.DATABASE_EXISTS()),
163+
removeInfiniteNotification(InfiniteMessagesIds.oAuthProgress),
164+
]
165+
expect(clearStoreActions(store.getActions())).toEqual(
166+
clearStoreActions(expectedActions)
167+
)
168+
})
169+
170+
it('should call addInfiniteNotification and removeInfiniteNotification when errorCode is 11_114', async () => {
171+
const mockDatabaseId = '123'
172+
const error = {
173+
errorCode: CustomErrorCodes.CloudSubscriptionAlreadyExistsFree,
174+
resource: {
175+
databaseId: mockDatabaseId
176+
}
177+
};
178+
(oauthCloudJobSelector as jest.Mock).mockImplementation(() => ({
179+
status: ''
180+
}))
181+
182+
const { rerender } = render(<OAuthJobs />);
183+
184+
(oauthCloudJobSelector as jest.Mock).mockImplementation(() => ({
185+
status: CloudJobStatus.Failed,
186+
error,
187+
}))
188+
189+
rerender(<OAuthJobs />)
190+
191+
const expectedActions = [
192+
addInfiniteNotification(INFINITE_MESSAGES.DATABASE_EXISTS()),
193+
removeInfiniteNotification(InfiniteMessagesIds.oAuthProgress),
133194
]
134195
expect(clearStoreActions(store.getActions())).toEqual(
135196
clearStoreActions(expectedActions)

0 commit comments

Comments
 (0)