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
40 changes: 40 additions & 0 deletions redisinsight/api/src/modules/database/dto/redis-info.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

class RedisDatabaseStatsDto {
@ApiProperty({
type: String,
})
instantaneous_input_kbps: string | undefined;

@ApiProperty({
type: String,
})
instantaneous_ops_per_sec: string | undefined;

@ApiProperty({
type: String,
})
instantaneous_output_kbps: string | undefined;

@ApiProperty({
type: Number,
})
maxmemory_policy: string | undefined;

@ApiProperty({
description: 'Redis database mode',
type: String,
})
numberOfKeysRange: string | undefined;

@ApiProperty({
description: 'Redis database role',
type: String,
})
uptime_in_days: string | undefined;
}

export class RedisNodeInfoResponse {
@ApiProperty({
description: 'Redis database version',
Expand All @@ -22,6 +56,12 @@ export class RedisNodeInfoResponse {
})
server?: any;

@ApiPropertyOptional({
description: 'Various Redis stats',
type: RedisDatabaseStatsDto,
})
stats?: RedisDatabaseStatsDto;

@ApiPropertyOptional({
description: 'The number of Redis databases',
type: Number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@ const mockRedisServerInfoDto = {
tcp_port: '11113',
uptime_in_seconds: '1000',
};
const mockRedisStatsDto = {
instantaneous_input_kbps: undefined,
instantaneous_ops_per_sec: undefined,
instantaneous_output_kbps: undefined,
maxmemory_policy: undefined,
numberOfKeysRange: '0 - 500 000',
uptime_in_days: undefined,
};

const mockRedisGeneralInfo: RedisDatabaseInfoResponse = {
version: mockRedisServerInfoDto.redis_version,
databases: 16,
role: 'master',
server: mockRedisServerInfoDto,
stats: mockRedisStatsDto,
usedMemory: 1000000,
totalKeys: 1,
connectedClients: 1,
Expand Down Expand Up @@ -441,6 +450,10 @@ describe('DatabaseInfoProvider', () => {

expect(result).toEqual({
...mockRedisGeneralInfo,
stats: {
...mockRedisStatsDto,
numberOfKeysRange: undefined,
},
totalKeys: undefined,
usedMemory: undefined,
hitRatio: undefined,
Expand Down Expand Up @@ -481,6 +494,10 @@ describe('DatabaseInfoProvider', () => {

expect(result).toEqual({
...mockRedisGeneralInfo,
stats: {
...mockRedisStatsDto,
numberOfKeysRange: undefined,
},
server: {
redis_mode: mockRedisServerInfoDto.redis_mode,
redis_version: mockRedisGeneralInfo.version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
calculateRedisHitRatio,
catchAclError,
convertIntToSemanticVersion,
getRangeForNumber,
TOTAL_KEYS_BREAKPOINTS,
} from 'src/utils';
import { AdditionalRedisModule } from 'src/modules/database/models/additional.redis.module';
import { REDIS_MODULES_COMMANDS, SUPPORTED_REDIS_MODULES } from 'src/constants';
Expand Down Expand Up @@ -162,11 +164,12 @@ export class DatabaseInfoProvider {
const statsInfo = info['stats'];
const replicationInfo = info['replication'];
const databases = await this.getDatabasesCount(client, keyspaceInfo);
const totalKeys = this.getRedisNodeTotalKeysCount(keyspaceInfo);
return {
version: serverInfo?.redis_version,
databases,
role: get(replicationInfo, 'role') || undefined,
totalKeys: this.getRedisNodeTotalKeysCount(keyspaceInfo),
role: get(replicationInfo, 'role'),
totalKeys,
usedMemory: parseInt(get(memoryInfo, 'used_memory'), 10) || undefined,
connectedClients:
parseInt(get(clientsInfo, 'connected_clients'), 10) || undefined,
Expand All @@ -177,6 +180,23 @@ export class DatabaseInfoProvider {
parseInt(get(memoryInfo, 'number_of_cached_scripts'), 10) ||
undefined,
server: serverInfo,
stats: {
instantaneous_ops_per_sec: get(
statsInfo,
'instantaneous_ops_per_sec',
),
instantaneous_input_kbps: get(statsInfo, 'instantaneous_input_kbps'),
instantaneous_output_kbps: get(
statsInfo,
'instantaneous_output_kbps',
),
uptime_in_days: get(serverInfo, 'uptime_in_days', undefined),
maxmemory_policy: get(memoryInfo, 'maxmemory_policy', undefined),
numberOfKeysRange: getRangeForNumber(
totalKeys,
TOTAL_KEYS_BREAKPOINTS,
),
},
};
} catch (error) {
throw catchAclError(error);
Expand Down
1 change: 0 additions & 1 deletion redisinsight/ui/src/components/base/layout/list/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ const Item = ({
$isActive={isActive}
$isDisabled={isDisabled}
$color={color}
onClick={onClick}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is deleted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because if it is left, it registers twice

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the itemContent have onclick only in the if statement? What else is triggering the same onclick?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I saw, there are 2 inner components based on the props:

  • if disabled = true and there is an onClick handler, it renders a button
  • else - renders a span

Probably this is the reason that there were 2 invocations on the handler. The tricky part is the if (isDisabled || onClick) { condition, but if it's disabled, we probably don't want to invoke the handler, so we're safe. I'd guard it with a test, tho.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is on click handler provided, the button that handles it is rendered, if no onClick is provided, nothing needs to handle it

className={cx(ListClassNames.listItem, className, {
[ListClassNames.listItemActive]: isActive,
[ListClassNames.listItemDisabled]: isDisabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
render,
screen,
} from 'uiSrc/utils/test-utils'
import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { Instance, RdiInstance } from 'uiSrc/slices/interfaces'
import InstancesList, { InstancesListProps } from './InstancesList'
import { InstancesTabs } from '../../InstancesNavigationPopover'

Expand All @@ -22,25 +23,39 @@ beforeEach(() => {
store.clearActions()
})

const mockRdis = [
const mockRdis: RdiInstance[] = [
{
id: 'rdiDB_1',
name: 'RdiDB_1',
loading: false,
error: '',
url: '',
},
{
id: 'rdiDB_2',
name: 'RdiDB_2',
loading: false,
error: '',
url: '',
},
]

const mockDbs = [
const mockDbs: Instance[] = [
{
id: 'db_1',
name: 'DB_1',
host: 'localhost',
port: 6379,
modules: [],
version: '7.0.0',
},
{
id: 'db_2',
name: 'DB_2',
host: 'localhost',
port: 6379,
modules: [],
version: '7.0.0',
},
]

Expand Down Expand Up @@ -85,7 +100,7 @@ describe('InstancesList', () => {
/>,
)

expect(screen.getByText(mockDbs[0].name)).toBeInTheDocument()
expect(screen.getByText(mockDbs[0].name!)).toBeInTheDocument()
})

it('should render rdi instances when selected tab is rdi', () => {
Expand All @@ -99,26 +114,24 @@ describe('InstancesList', () => {
expect(screen.getByText(mockRdis[0].name)).toBeInTheDocument()
})

it('should send event telemetry', () => {
it('should send event telemetry', async () => {
const sendEventTelemetryMock = jest.fn()
;(sendEventTelemetry as jest.Mock).mockImplementation(
() => sendEventTelemetryMock,
)

render(
const { getByTestId } = render(
<InstancesList
{...instance(mockedProps)}
selectedTab={InstancesTabs.Databases}
filteredDbInstances={mockDbs}
/>,
)

const listItem = screen.getByTestId(`instance-item-${mockDbs[1].id}`)
act(() => {
fireEvent.click(listItem)
})
const listItem = getByTestId(`instance-item-${mockDbs[1].id}`)
await act(() => fireEvent.click(listItem))

expect(sendEventTelemetry).toBeCalledWith({
expect(sendEventTelemetry).toHaveBeenCalledWith({
event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE,
eventData: expect.any(Object),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
TelemetryEvent,
getRedisModulesSummary,
sendEventTelemetry,
getRedisInfoSummary,
} from 'uiSrc/telemetry'
import { getDbIndex } from 'uiSrc/utils'
import {
Expand Down Expand Up @@ -56,20 +57,22 @@ const InstancesList = ({
history.push(Pages.browser(id))
}

const goToInstance = (instance: Instance) => {
const goToInstance = async (instance: Instance) => {
if (instanceId === instance.id) {
// already connected so do nothing
return
}
setLoading(true)
const modulesSummary = getRedisModulesSummary(instance.modules)
sendEventTelemetry({
const infoData = await getRedisInfoSummary(instance.id)
await sendEventTelemetry({
event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE,
eventData: {
databaseId: instance.id,
source: 'navigation_panel',
provider: instance.provider,
...modulesSummary,
...infoData,
},
})
dispatch(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import React from 'react'
import { cloneDeep } from 'lodash'
import { cleanup, fireEvent, mockedStore, render } from 'uiSrc/utils/test-utils'
import {
TelemetryEvent,
act,
cleanup,
fireEvent,
mockedStore,
render,
} from 'uiSrc/utils/test-utils'
import {
getRedisModulesSummary,
sendEventTelemetry,
TelemetryEvent,
} from 'uiSrc/telemetry'
import {
freeInstancesSelector,
setDefaultInstance,
} from 'uiSrc/slices/instances/instances'
import { OAuthSocialSource } from 'uiSrc/slices/interfaces'
import { setCapability } from 'uiSrc/slices/app/context'
import { MOCK_ADDITIONAL_INFO } from 'uiSrc/mocks/data/instances'
import OAuthConnectFreeDb from './OAuthConnectFreeDb'

jest.mock('uiSrc/telemetry', () => ({
Expand Down Expand Up @@ -56,15 +63,20 @@ describe('OAuthConnectFreeDb', () => {

const { queryByTestId } = render(<OAuthConnectFreeDb id="providedId" />)

fireEvent.click(queryByTestId('connect-free-db-btn') as HTMLButtonElement)
await act(() =>
fireEvent.click(
queryByTestId('connect-free-db-btn') as HTMLButtonElement,
),
)

expect(sendEventTelemetry).toBeCalledWith({
expect(sendEventTelemetry).toHaveBeenCalledWith({
event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE,
eventData: {
databaseId: 'providedId',
provider: undefined,
source: OAuthSocialSource.ListOfDatabases,
...getRedisModulesSummary(),
...MOCK_ADDITIONAL_INFO,
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TelemetryEvent,
getRedisModulesSummary,
sendEventTelemetry,
getRedisInfoSummary,
} from 'uiSrc/telemetry'
import { OAuthSocialSource } from 'uiSrc/slices/interfaces'
import {
Expand Down Expand Up @@ -48,15 +49,17 @@ const OAuthConnectFreeDb = ({
return null
}

const sendTelemetry = () => {
const sendTelemetry = async () => {
const modulesSummary = getRedisModulesSummary(modules)
const infoData = await getRedisInfoSummary(targetDatabaseId)
sendEventTelemetry({
event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE,
eventData: {
databaseId: targetDatabaseId,
provider,
source,
...modulesSummary,
...infoData,
},
})
}
Expand All @@ -67,8 +70,8 @@ const OAuthConnectFreeDb = ({
openNewWindowDatabase(Pages.browser(targetDatabaseId) + search)
}

const handleCheckConnectToInstance = () => {
sendTelemetry()
const handleCheckConnectToInstance = async () => {
await sendTelemetry()
dispatch(setCapability({ source, tutorialPopoverShown: false }))
dispatch(
checkConnectToInstanceAction(
Expand Down
27 changes: 27 additions & 0 deletions redisinsight/ui/src/mocks/data/instances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const MOCK_INFO_API_RESPONSE = {
version: '7.4.0',
usedMemory: 1234,
connectedClients: 2,
totalKeys: 123,
stats: {
uptime_in_days: '2',
maxmemory_policy: 'allkeys-lru',
instantaneous_ops_per_sec: 123,
instantaneous_input_kbps: 123,
instantaneous_output_kbps: 123,
numberOfKeysRange: '0-123',
},
}

export const MOCK_ADDITIONAL_INFO = {
connected_clients: 2,
instantaneous_input_kbps: 123,
instantaneous_ops_per_sec: 123,
instantaneous_output_kbps: 123,
maxmemory_policy: 'allkeys-lru',
numberOfKeysRange: '0-123',
redis_version: '7.4.0',
totalKeys: 123,
uptime_in_days: '2',
used_memory: 1234,
}
Loading
Loading