Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion redisinsight/api/src/modules/rdi/dto/update.rdi.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { OmitType, PartialType } from '@nestjs/swagger';
import { Rdi } from 'src/modules/rdi/models';

export class UpdateRdiDto extends PartialType(OmitType(Rdi, [
'id', 'lastConnection',
'id', 'lastConnection', 'url', 'version',
] as const)) {}
26 changes: 20 additions & 6 deletions redisinsight/api/src/modules/rdi/rdi.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,30 @@ import { RdiClientProvider } from 'src/modules/rdi/providers/rdi.client.provider
import { RdiClientFactory } from 'src/modules/rdi/providers/rdi.client.factory';
import { SessionMetadata } from 'src/common/models';
import { wrapRdiPipelineError } from 'src/modules/rdi/exceptions';
import { isUndefined, omitBy } from 'lodash';
import { deepMerge } from 'src/common/utils';
import { RdiAnalytics } from './rdi.analytics';

@Injectable()
export class RdiService {
private logger = new Logger('RdiService');

static connectionFields: string[] = [
'username',
'password',
];

constructor(
private readonly repository: RdiRepository,
private readonly analytics: RdiAnalytics,
private readonly rdiClientProvider: RdiClientProvider,
private readonly rdiClientFactory: RdiClientFactory,
) {}

static isConnectionAffected(dto: UpdateRdiDto) {
return Object.keys(omitBy(dto, isUndefined)).some((field) => this.connectionFields.includes(field));
}

async list(): Promise<Rdi[]> {
return await this.repository.list();
}
Expand All @@ -41,17 +52,20 @@ export class RdiService {
}

async update(rdiClientMetadata: RdiClientMetadata, dto: UpdateRdiDto): Promise<Rdi> {
// TODO update dto to get only updated fields
const model = classToClass(Rdi, dto);

await this.rdiClientProvider.delete(rdiClientMetadata);
const oldRdiInstance = await this.get(rdiClientMetadata.id);
const newRdiInstance = await deepMerge(oldRdiInstance, dto);

try {
await this.rdiClientFactory.createClient(rdiClientMetadata, model);
if (RdiService.isConnectionAffected(dto)) {
await this.rdiClientFactory.createClient(rdiClientMetadata, newRdiInstance);
await this.rdiClientProvider.deleteManyByRdiId(rdiClientMetadata.id);
}

return await this.repository.update(rdiClientMetadata.id, newRdiInstance);
} catch (error) {
this.logger.error(`Failed to update rdi instance ${rdiClientMetadata.id}`, error);
throw wrapRdiPipelineError(error);
}
return await this.repository.update(rdiClientMetadata.id, model);
}

async create(sessionMetadata: SessionMetadata, dto: CreateRdiDto): Promise<Rdi> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class LocalRdiRepository extends RdiRepository {
/**
* @inheritDoc
*/
public async update(id: string, rdi: Partial<Rdi>): Promise<Rdi> {
public async update(id: string, rdi: Rdi): Promise<Rdi> {
const oldEntity = await this.modelEncryptor.decryptEntity((await this.repository.findOneBy({ id })), true);
const newEntity = classToClass(RdiEntity, rdi);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export abstract class RdiRepository {

/**
* Delete RDI by id
* @param id
* @param ids
*/
abstract delete(ids: string[]): Promise<void>;
}
10 changes: 2 additions & 8 deletions redisinsight/ui/src/pages/rdi/home/RdiPage.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe('RdiPage', () => {
expect(screen.getByTestId('connection-form-password-input')).toHaveValue('')
})

it('should call edit instance when editInstance is provided', async () => {
it('should call edit instance with proper data when editInstance is provided', async () => {
render(<RdiPage />)

fireEvent.click(screen.getByTestId('edit-instance-1'))
Expand All @@ -196,16 +196,10 @@ describe('RdiPage', () => {
})

expect(editInstanceAction).toBeCalledWith(
'1',
{
id: '1',
lastConnection: new Date('1/1/2024'),
name: 'name',
password: 'password2',
url: 'redis-12345.c253.us-central1-1.gce.cloud.redislabs.com:12345',
username: 'user',
version: '1.2',
visible: true,
error: ''
},
expect.any(Function)
)
Expand Down
9 changes: 5 additions & 4 deletions redisinsight/ui/src/pages/rdi/home/RdiPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
sendPageViewTelemetry
} from 'uiSrc/telemetry'
import HomePageTemplate from 'uiSrc/templates/home-page-template'
import { setTitle } from 'uiSrc/utils'
import { getFormUpdates, setTitle } from 'uiSrc/utils'
import EmptyMessage from './empty-message/EmptyMessage'
import ConnectionForm from './connection-form/ConnectionForm'
import RdiHeader from './header/RdiHeader'
Expand Down Expand Up @@ -47,14 +47,15 @@ const RdiPage = () => {
setWidth(innerWidth)
}

const handleAddInstance = (instance: Partial<RdiInstance>) => {
const handleFormSubmit = (instance: Partial<RdiInstance>) => {
const onSuccess = () => {
setIsConnectionFormOpen(false)
setEditInstance(null)
}

if (editInstance) {
dispatch(editInstanceAction({ ...editInstance, ...instance }, onSuccess))
const payload = getFormUpdates(instance, editInstance)
dispatch(editInstanceAction(editInstance.id, payload, onSuccess))
} else {
dispatch(createInstanceAction({ ...instance }, onSuccess))
}
Expand Down Expand Up @@ -153,7 +154,7 @@ const RdiPage = () => {
>
{isConnectionFormOpen && (
<ConnectionForm
onAddInstance={handleAddInstance}
onSubmit={handleFormSubmit}
onCancel={handleCloseConnectionForm}
editInstance={editInstance}
isLoading={loading || loadingChanging}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { act, fireEvent, render, screen, waitFor } from 'uiSrc/utils/test-utils'
import ConnectionForm, { Props } from './ConnectionForm'

const mockedProps: Props = {
onAddInstance: jest.fn(),
onSubmit: jest.fn(),
onCancel: jest.fn(),
isLoading: false,
editInstance: null
Expand Down Expand Up @@ -34,7 +34,8 @@ describe('ConnectionForm', () => {
})
})

it('should disable test connection button when form is invalid', async () => {
// TODO update when add test connection endpoint
it.skip('should disable test connection button when form is invalid', async () => {
render(<ConnectionForm {...mockedProps} />)

await waitFor(() => {
Expand Down Expand Up @@ -91,7 +92,7 @@ describe('ConnectionForm', () => {
expect(tooltip).toBeInTheDocument()
})

it('should show validation tooltip when test connection button is disabled', async () => {
it.skip('should show validation tooltip when test connection button is disabled', async () => {
render(<ConnectionForm {...mockedProps} />)

fireEvent.mouseOver(screen.getByTestId('connection-form-test-button'))
Expand All @@ -107,7 +108,7 @@ describe('ConnectionForm', () => {
expect(screen.getByTestId('connection-form-add-button')).toBeDisabled()
})

it('should disable test connection button when isLoading = true', async () => {
it.skip('should disable test connection button when isLoading = true', async () => {
render(<ConnectionForm {...mockedProps} isLoading />)

expect(screen.getByTestId('connection-form-test-button')).toBeDisabled()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
EuiToolTip
} from '@elastic/eui'
import { Field, FieldInputProps, FieldMetaProps, Form, Formik, FormikErrors, FormikHelpers } from 'formik'
import { omit } from 'lodash'
import React, { useEffect, useState } from 'react'

import cx from 'classnames'
Expand All @@ -29,7 +28,7 @@ export interface ConnectionFormValues {
}

export interface Props {
onAddInstance: (instance: Partial<RdiInstance>) => void
onSubmit: (instance: Partial<RdiInstance>) => void
onCancel: () => void
editInstance: RdiInstance | null
isLoading: boolean
Expand Down Expand Up @@ -69,18 +68,15 @@ const UrlTooltip = () => (
</EuiToolTip>
)

const ConnectionForm = ({ onAddInstance, onCancel, editInstance, isLoading }: Props) => {
const ConnectionForm = (props: Props) => {
const { onSubmit, onCancel, editInstance, isLoading } = props

const [initialFormValues, setInitialFormValues] = useState(getInitialValues(editInstance))
const [passwordChanged, setPasswordChanged] = useState(false)

useEffect(() => {
setInitialFormValues(getInitialValues(editInstance))
}, [editInstance])

const onSubmit = (formValues: ConnectionFormValues) => {
onAddInstance({ ...omit(formValues, !passwordChanged ? 'password' : '') })
}

const validate = (values: ConnectionFormValues) => {
const errors: FormikErrors<ConnectionFormValues> = {}

Expand Down Expand Up @@ -167,60 +163,58 @@ const ConnectionForm = ({ onAddInstance, onCancel, editInstance, isLoading }: Pr
meta: FieldMetaProps<string>
}) => (
<EuiFieldPassword
data-testid="connection-form-password-input"
className={styles.passwordField}
fullWidth
placeholder="Enter Password"
maxLength={500}
{...field}
onFocus={() => {
if (field.value === SECURITY_FIELD && !meta.touched) {
form.setFieldValue('password', '')
}

setPasswordChanged(true)
}}
/>
data-testid="connection-form-password-input"
className={styles.passwordField}
fullWidth
placeholder="Enter Password"
maxLength={500}
{...field}
onFocus={() => {
if (field.value === SECURITY_FIELD && !meta.touched) {
form.setFieldValue('password', '')
}
}}
/>
)}
</Field>
</EuiFormRow>
</div>
<div>
<EuiFlexGroup className="footerAddDatabase" gutterSize="none" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<ValidationTooltip isValid={isValid} errors={errors}>
<EuiButton
data-testid="connection-form-test-button"
className={styles.testConnectionBtn}
iconType={!isValid ? 'iInCircle' : undefined}
isLoading={isLoading}
disabled={!isValid}
>
Test Connection
</EuiButton>
</ValidationTooltip>
{/* <ValidationTooltip isValid={isValid} errors={errors}> */}
{/* <EuiButton */}
{/* data-testid="connection-form-test-button" */}
{/* className={styles.testConnectionBtn} */}
{/* iconType={!isValid ? 'iInCircle' : undefined} */}
{/* isLoading={isLoading} */}
{/* disabled={!isValid} */}
{/* > */}
{/* Test Connection */}
{/* </EuiButton> */}
{/* </ValidationTooltip> */}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButton color="secondary" data-testid="connection-form-cancel-button" onClick={onCancel}>
Cancel
</EuiButton>
Cancel
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ValidationTooltip isValid={isValid} errors={errors}>
<EuiButton
data-testid="connection-form-add-button"
type="submit"
fill
color="secondary"
iconType={!isValid ? 'iInCircle' : undefined}
isLoading={isLoading}
disabled={!isValid}
>
Add Instance
</EuiButton>
</ValidationTooltip>
<EuiButton
data-testid="connection-form-add-button"
type="submit"
fill
color="secondary"
iconType={!isValid ? 'iInCircle' : undefined}
isLoading={isLoading}
disabled={!isValid}
>
{editInstance ? 'Apply Changes' : 'Add Instance'}
</EuiButton>
</ValidationTooltip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
3 changes: 2 additions & 1 deletion redisinsight/ui/src/slices/rdi/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ export function createInstanceAction(payload: Partial<RdiInstance>, onSuccess?:

// Asynchronous thunk action
export function editInstanceAction(
{ id, ...payload }: Partial<RdiInstance>,
id: string,
payload: Partial<RdiInstance>,
onSuccess?: (data: RdiInstanceResponse) => void
) {
return async (dispatch: AppDispatch) => {
Expand Down