Skip to content

Commit 25c426e

Browse files
committed
feat(ui): add index info box
- integrated Section component from Redis UI to represent the index information - show summary info for the index, when the section is collapsed - show details about the index attributes when the section is uncolapped re #RI-7197
1 parent 92b1c59 commit 25c426e

File tree

10 files changed

+584
-18
lines changed

10 files changed

+584
-18
lines changed

redisinsight/api/src/modules/browser/redisearch/dto/index.info.dto.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ export class IndexDefinitionDto {
5555
})
5656
@Expose()
5757
default_score: string;
58+
59+
@ApiProperty({
60+
description:
61+
'Indicates whether all fields of a JSON document are automatically indexed by RediSearch',
62+
type: String,
63+
})
64+
@Expose()
65+
indexes_all?: string;
5866
}
5967

6068
export class IndexAttibuteDto {
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { IndexInfoDto } from 'apiSrc/modules/browser/redisearch/dto'
2+
3+
// TODO: Rework this mock to use factory and faker later
4+
export const MOCK_REDISEARCH_INDEX_INFO: IndexInfoDto = {
5+
index_name: 'idx:smpl_bicycle',
6+
index_options: {},
7+
index_definition: {
8+
key_type: 'JSON',
9+
prefixes: ['sample_bicycle:'],
10+
default_score: '1',
11+
indexes_all: 'false',
12+
},
13+
attributes: [
14+
{
15+
identifier: '$.brand',
16+
attribute: 'brand',
17+
type: 'TEXT',
18+
WEIGHT: '1',
19+
SEPARATOR: ',',
20+
},
21+
{
22+
identifier: '$.model',
23+
attribute: 'model',
24+
type: 'TEXT',
25+
WEIGHT: '1',
26+
},
27+
{
28+
identifier: '$.description',
29+
attribute: 'description',
30+
type: 'TEXT',
31+
WEIGHT: '1',
32+
},
33+
{
34+
identifier: '$.price',
35+
attribute: 'price',
36+
type: 'NUMERIC',
37+
},
38+
{
39+
identifier: '$.condition',
40+
attribute: 'condition',
41+
type: 'TAG',
42+
SEPARATOR: ',',
43+
},
44+
{
45+
identifier: '$.type',
46+
attribute: 'type',
47+
type: 'TAG',
48+
SEPARATOR: '',
49+
},
50+
{
51+
identifier: '$.helmet_included',
52+
attribute: 'helmet_included',
53+
type: 'TAG',
54+
SEPARATOR: '',
55+
},
56+
{
57+
identifier: '$.specs.material',
58+
attribute: 'material',
59+
type: 'TAG',
60+
SEPARATOR: '',
61+
},
62+
{
63+
identifier: '$.specs.weight',
64+
attribute: 'weight',
65+
type: 'NUMERIC',
66+
},
67+
],
68+
num_docs: '106', // Note: DTO and actual response have different types, it should be a number
69+
max_doc_id: '106', // Note: DTO and actual response have different types, it should be a number
70+
num_terms: '539', // Note: DTO and actual response have different types, it should be a number
71+
num_records: '2645', // Note: DTO and actual response have different types, it should be a number
72+
inverted_sz_mb: '0.06543350219726563',
73+
vector_index_sz_mb: '0',
74+
total_inverted_index_blocks: '690', // Note: DTO and actual response have different types, it should be a number
75+
offset_vectors_sz_mb: '0.0022459030151367188',
76+
doc_table_size_mb: '0.023920059204101563',
77+
sortable_values_size_mb: '0',
78+
key_table_size_mb: '0.0032911300659179688',
79+
tag_overhead_sz_mb: '6.361007690429688e-4',
80+
text_overhead_sz_mb: '0.017991065979003906',
81+
total_index_memory_sz_mb: '0.11714744567871094',
82+
geoshapes_sz_mb: '0',
83+
records_per_doc_avg: '24.952829360961914',
84+
bytes_per_record_avg: '25.940263748168945',
85+
offsets_per_term_avg: '0.8903591632843018',
86+
offset_bits_per_record_avg: '8',
87+
hash_indexing_failures: '0', // Note: DTO and actual response have different types, it should be a number
88+
total_indexing_time: '1.7289999723434448',
89+
indexing: '0', // Note: DTO and actual response have different types, it should be a number
90+
percent_indexed: '1',
91+
number_of_uses: 39,
92+
cleaning: 0,
93+
gc_stats: {
94+
bytes_collected: '0',
95+
total_ms_run: '0',
96+
total_cycles: '0',
97+
average_cycle_time_ms: 'nan',
98+
last_run_time_ms: '0',
99+
gc_numeric_trees_missed: '0',
100+
gc_blocks_denied: '0',
101+
},
102+
cursor_stats: {
103+
global_idle: 0,
104+
global_total: 0,
105+
index_capacity: 128,
106+
index_total: 0,
107+
},
108+
dialect_stats: {
109+
dialect_1: 0,
110+
dialect_2: 0,
111+
dialect_3: 0,
112+
dialect_4: 0,
113+
},
114+
'Index Errors': {
115+
'indexing failures': 0,
116+
'last indexing error': 'N/A',
117+
'last indexing error key': 'N/A',
118+
'background indexing status': 'OK',
119+
},
120+
'field statistics': [
121+
{
122+
identifier: '$.brand',
123+
attribute: 'brand',
124+
'Index Errors': {
125+
'indexing failures': 0,
126+
'last indexing error': 'N/A',
127+
'last indexing error key': 'N/A',
128+
},
129+
},
130+
{
131+
identifier: '$.model',
132+
attribute: 'model',
133+
'Index Errors': {
134+
'indexing failures': 0,
135+
'last indexing error': 'N/A',
136+
'last indexing error key': 'N/A',
137+
},
138+
},
139+
{
140+
identifier: '$.description',
141+
attribute: 'description',
142+
'Index Errors': {
143+
'indexing failures': 0,
144+
'last indexing error': 'N/A',
145+
'last indexing error key': 'N/A',
146+
},
147+
},
148+
{
149+
identifier: '$.price',
150+
attribute: 'price',
151+
'Index Errors': {
152+
'indexing failures': 0,
153+
'last indexing error': 'N/A',
154+
'last indexing error key': 'N/A',
155+
},
156+
},
157+
{
158+
identifier: '$.condition',
159+
attribute: 'condition',
160+
'Index Errors': {
161+
'indexing failures': 0,
162+
'last indexing error': 'N/A',
163+
'last indexing error key': 'N/A',
164+
},
165+
},
166+
{
167+
identifier: '$.type',
168+
attribute: 'type',
169+
'Index Errors': {
170+
'indexing failures': 0,
171+
'last indexing error': 'N/A',
172+
'last indexing error key': 'N/A',
173+
},
174+
},
175+
{
176+
identifier: '$.helmet_included',
177+
attribute: 'helmet_included',
178+
'Index Errors': {
179+
'indexing failures': 0,
180+
'last indexing error': 'N/A',
181+
'last indexing error key': 'N/A',
182+
},
183+
},
184+
{
185+
identifier: '$.specs.material',
186+
attribute: 'material',
187+
'Index Errors': {
188+
'indexing failures': 0,
189+
'last indexing error': 'N/A',
190+
'last indexing error key': 'N/A',
191+
},
192+
},
193+
{
194+
identifier: '$.specs.weight',
195+
attribute: 'weight',
196+
'Index Errors': {
197+
'indexing failures': 0,
198+
'last indexing error': 'N/A',
199+
'last indexing error key': 'N/A',
200+
},
201+
},
202+
],
203+
}

redisinsight/ui/src/mocks/handlers/browser/redisearchHandlers.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { rest, RestHandler } from 'msw'
22
import { ApiEndpoints } from 'uiSrc/constants'
33
import { getMswURL } from 'uiSrc/utils/test-utils'
44
import { getUrl, stringToBuffer } from 'uiSrc/utils'
5-
import { ListRedisearchIndexesResponse } from 'apiSrc/modules/browser/redisearch/dto'
5+
import { MOCK_REDISEARCH_INDEX_INFO } from 'uiSrc/mocks/data/redisearch'
6+
import {
7+
IndexInfoDto,
8+
ListRedisearchIndexesResponse,
9+
} from 'apiSrc/modules/browser/redisearch/dto'
610
import { INSTANCE_ID_MOCK } from '../instances/instancesHandlers'
711

812
export const REDISEARCH_LIST_DATA_MOCK_UTF8 = ['idx: 1', 'idx:2']
@@ -16,9 +20,14 @@ const handlers: RestHandler[] = [
1620
// fetchRedisearchListAction
1721
rest.get<ListRedisearchIndexesResponse>(
1822
getMswURL(getUrl(INSTANCE_ID_MOCK, ApiEndpoints.REDISEARCH)),
19-
async (req, res, ctx) =>
23+
async (_req, res, ctx) =>
2024
res(ctx.status(200), ctx.json(REDISEARCH_LIST_DATA_MOCK)),
2125
),
26+
rest.post<IndexInfoDto>(
27+
getMswURL(getUrl(INSTANCE_ID_MOCK, ApiEndpoints.REDISEARCH_INFO)),
28+
async (_req, res, ctx) =>
29+
res(ctx.status(200), ctx.json(MOCK_REDISEARCH_INDEX_INFO)),
30+
),
2231
]
2332

2433
export default handlers
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react'
2+
import { cleanup, render, screen } from 'uiSrc/utils/test-utils'
3+
import {
4+
IndexAttributesList,
5+
IndexAttributesListProps,
6+
} from './IndexAttributesList'
7+
8+
const renderComponent = (props?: Partial<IndexAttributesListProps>) => {
9+
// TODO: Potentially replace this with a factory later
10+
const defaultProps: IndexAttributesListProps = {
11+
data: [
12+
{
13+
attribute: 'title',
14+
type: 'TEXT',
15+
weight: '1.0',
16+
separator: ',',
17+
},
18+
{
19+
attribute: 'content',
20+
type: 'TEXT',
21+
},
22+
{
23+
attribute: 'tags',
24+
type: 'TAG',
25+
weight: '1.0',
26+
},
27+
],
28+
}
29+
30+
return render(<IndexAttributesList {...defaultProps} {...props} />)
31+
}
32+
33+
describe('IndexAttributesList', () => {
34+
beforeEach(() => {
35+
cleanup()
36+
})
37+
38+
it('should render', () => {
39+
const props: IndexAttributesListProps = {
40+
data: [
41+
{
42+
attribute: 'title',
43+
type: 'TEXT',
44+
weight: '1.0',
45+
separator: ',',
46+
},
47+
],
48+
}
49+
50+
const { container } = renderComponent(props)
51+
expect(container).toBeTruthy()
52+
53+
const list = screen.getByTestId('index-attributes-list')
54+
expect(list).toBeInTheDocument()
55+
56+
// Verify data is rendered correctly
57+
const attribute = screen.getByText(props.data[0].attribute)
58+
const type = screen.getByText(props.data[0].type)
59+
const weight = screen.getByText(props.data[0].weight!)
60+
const separator = screen.getByText(props.data[0].separator!)
61+
62+
expect(attribute).toBeInTheDocument()
63+
expect(type).toBeInTheDocument()
64+
expect(weight).toBeInTheDocument()
65+
expect(separator).toBeInTheDocument()
66+
})
67+
})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import styled from 'styled-components'
2+
3+
export const StyledIndexAttributesList = styled.div`
4+
> *:first-child {
5+
box-shadow: none !important;
6+
border-radius: 0;
7+
margin: -30px 0;
8+
}
9+
`
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react'
2+
import { ColumnDefinition, Table } from 'uiSrc/components/base/layout/table'
3+
import { StyledIndexAttributesList } from './IndexAttributesList.styles'
4+
5+
export interface IndexInfoTableData {
6+
attribute: string
7+
type: string
8+
weight?: string
9+
separator?: string
10+
}
11+
12+
const tableColumns: ColumnDefinition<IndexInfoTableData>[] = [
13+
{
14+
header: 'Attribute',
15+
id: 'attribute',
16+
accessorKey: 'attribute',
17+
},
18+
{
19+
header: 'Type',
20+
id: 'type',
21+
accessorKey: 'type',
22+
enableSorting: false,
23+
},
24+
{
25+
header: 'Weight',
26+
id: 'weight',
27+
accessorKey: 'weight',
28+
enableSorting: false,
29+
},
30+
{
31+
header: 'Separator',
32+
id: 'separator',
33+
accessorKey: 'separator',
34+
enableSorting: false,
35+
},
36+
]
37+
38+
export interface IndexAttributesListProps {
39+
data: IndexInfoTableData[]
40+
}
41+
42+
export const IndexAttributesList = ({ data }: IndexAttributesListProps) => (
43+
// @ts-ignore - styled-components typing issue, functionality works correctly
44+
<StyledIndexAttributesList data-testid="index-attributes-list">
45+
<Table columns={tableColumns} data={data} />
46+
</StyledIndexAttributesList>
47+
)

0 commit comments

Comments
 (0)