Skip to content

Commit 434da67

Browse files
authored
RI-6855: Add vector search flow (#4818)
Version 1 of the feature: - Predefined dataset - Limited index customisation
1 parent ebd4944 commit 434da67

File tree

151 files changed

+8762
-216
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

151 files changed

+8762
-216
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ module.exports = {
9191
'sonarjs/no-identical-functions': 'error',
9292
'sonarjs/prefer-immediate-return': 'error',
9393
'sonarjs/no-small-switch': 'error',
94+
'sonarjs/no-nested-template-literals': 'off',
9495
'no-console': 'error',
9596
'import/no-duplicates': 'error',
9697
'prefer-destructuring': 'error',

jest.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
'\\.scss\\?inline$': '<rootDir>/redisinsight/__mocks__/scssRaw.js',
1414
'uiSrc/slices/store$': '<rootDir>/redisinsight/ui/src/utils/test-store.ts',
1515
'uiSrc/(.*)': '<rootDir>/redisinsight/ui/src/$1',
16+
'apiSrc/(.*)': '<rootDir>/redisinsight/api/src/$1',
1617
'@redislabsdev/redis-ui-components': '@redis-ui/components',
1718
'@redislabsdev/redis-ui-styles': '@redis-ui/styles',
1819
'@redislabsdev/redis-ui-icons': '@redis-ui/icons',

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"@babel/preset-react": "^7.22.15",
108108
"@babel/preset-typescript": "^7.23.2",
109109
"@electron/rebuild": "^3.7.1",
110+
"@faker-js/faker": "^9.9.0",
110111
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
111112
"@svgr/webpack": "^8.1.0",
112113
"@teamsupercell/typings-for-css-modules-loader": "^2.4.0",
@@ -181,6 +182,7 @@
181182
"eslint-plugin-react-hooks": "^5.0.0",
182183
"eslint-plugin-sonarjs": "^2.0.4",
183184
"file-loader": "^6.0.0",
185+
"fishery": "^2.3.1",
184186
"google-auth-library": "^9.0.0",
185187
"googleapis": "^125.0.0",
186188
"html-webpack-plugin": "^5.6.0",

redisinsight/api/data/vector-collections/bikes

Lines changed: 111 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsDefined } from 'class-validator';
3+
import { RedisString } from 'src/common/constants';
4+
import { IsRedisString, RedisStringType } from 'src/common/decorators';
5+
6+
export class IndexDeleteRequestBodyDto {
7+
@ApiProperty({
8+
description: 'Index name',
9+
type: String,
10+
})
11+
@IsDefined()
12+
@RedisStringType()
13+
@IsRedisString()
14+
index: RedisString;
15+
}

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 {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './create.redisearch-index.dto';
22
export * from './search.redisearch.dto';
33
export * from './list.redisearch-indexes.response';
44
export * from './index.info.dto';
5+
export * from './index.delete.dto';

redisinsight/api/src/modules/browser/redisearch/redisearch.controller.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Body,
33
Controller,
4+
Delete,
45
Get,
56
HttpCode,
67
Post,
@@ -24,6 +25,7 @@ import { RedisearchService } from 'src/modules/browser/redisearch/redisearch.ser
2425
import { ClientMetadata } from 'src/common/models';
2526
import { BrowserSerializeInterceptor } from 'src/common/interceptors';
2627
import { BrowserBaseController } from 'src/modules/browser/browser.base.controller';
28+
import { IndexDeleteRequestBodyDto } from './dto/index.delete.dto';
2729

2830
@ApiTags('Browser: RediSearch')
2931
@UseInterceptors(BrowserSerializeInterceptor)
@@ -80,4 +82,14 @@ export class RedisearchController extends BrowserBaseController {
8082
): Promise<IndexInfoDto> {
8183
return await this.service.getInfo(clientMetadata, dto);
8284
}
85+
86+
@Delete('')
87+
@HttpCode(204)
88+
@ApiOperation({ description: 'Delete index' })
89+
async delete(
90+
@BrowserClientMetadata() clientMetadata: ClientMetadata,
91+
@Body() dto: IndexDeleteRequestBodyDto,
92+
): Promise<void> {
93+
return await this.service.deleteIndex(clientMetadata, dto);
94+
}
8395
}

redisinsight/api/src/modules/browser/redisearch/redisearch.service.spec.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ConflictException,
44
ForbiddenException,
55
InternalServerErrorException,
6+
NotFoundException,
67
} from '@nestjs/common';
78
import { when } from 'jest-when';
89
import {
@@ -453,4 +454,71 @@ describe('RedisearchService', () => {
453454
).rejects.toThrow(InternalServerErrorException);
454455
});
455456
});
457+
458+
describe('deleteIndex', () => {
459+
it('should delete index for standalone', async () => {
460+
const mockIndexName = 'idx:movie';
461+
when(standaloneClient.sendCommand)
462+
.calledWith(expect.arrayContaining(['FT.DROPINDEX']))
463+
.mockResolvedValue(undefined);
464+
465+
await service.deleteIndex(mockBrowserClientMetadata, {
466+
index: mockIndexName,
467+
});
468+
469+
expect(standaloneClient.sendCommand).toHaveBeenCalledWith(
470+
['FT.DROPINDEX', mockIndexName],
471+
{ replyEncoding: 'utf8' },
472+
);
473+
});
474+
475+
it('should delete index for cluster', async () => {
476+
const mockIndexName = 'idx:movie';
477+
databaseClientFactory.getOrCreateClient = jest
478+
.fn()
479+
.mockResolvedValue(clusterClient);
480+
when(clusterClient.sendCommand)
481+
.calledWith(expect.arrayContaining(['FT.DROPINDEX']))
482+
.mockResolvedValue(undefined);
483+
484+
await service.deleteIndex(mockBrowserClientMetadata, {
485+
index: mockIndexName,
486+
});
487+
488+
expect(clusterClient.sendCommand).toHaveBeenCalledWith(
489+
['FT.DROPINDEX', mockIndexName],
490+
{ replyEncoding: 'utf8' },
491+
);
492+
});
493+
494+
it('should handle index not found error', async () => {
495+
const mockIndexName = 'idx:movie';
496+
when(standaloneClient.sendCommand)
497+
.calledWith(expect.arrayContaining(['FT.DROPINDEX']))
498+
.mockRejectedValue(mockRedisUnknownIndexName);
499+
500+
try {
501+
await service.deleteIndex(mockBrowserClientMetadata, {
502+
index: mockIndexName,
503+
});
504+
} catch (e) {
505+
expect(e).toBeInstanceOf(NotFoundException);
506+
}
507+
});
508+
509+
it('should handle ACL error', async () => {
510+
const mockIndexName = 'idx:movie';
511+
when(standaloneClient.sendCommand)
512+
.calledWith(expect.arrayContaining(['FT.DROPINDEX']))
513+
.mockRejectedValue(mockRedisNoPermError);
514+
515+
try {
516+
await service.deleteIndex(mockBrowserClientMetadata, {
517+
index: mockIndexName,
518+
});
519+
} catch (e) {
520+
expect(e).toBeInstanceOf(ForbiddenException);
521+
}
522+
});
523+
});
456524
});

redisinsight/api/src/modules/browser/redisearch/redisearch.service.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ConflictException,
44
Injectable,
55
Logger,
6+
NotFoundException,
67
} from '@nestjs/common';
78
import ERROR_MESSAGES from 'src/constants/error-messages';
89
import { catchRedisSearchError } from 'src/utils';
@@ -28,6 +29,7 @@ import {
2829
RedisClientNodeRole,
2930
} from 'src/modules/redis/client';
3031
import { convertIndexInfoReply } from '../utils/redisIndexInfo';
32+
import { IndexDeleteRequestBodyDto } from './dto/index.delete.dto';
3133

3234
@Injectable()
3335
export class RedisearchService {
@@ -269,6 +271,36 @@ export class RedisearchService {
269271
}
270272
}
271273

274+
public async deleteIndex(
275+
clientMetadata: ClientMetadata,
276+
dto: IndexDeleteRequestBodyDto,
277+
): Promise<void> {
278+
this.logger.debug('Deleting redisearch index ', clientMetadata);
279+
280+
try {
281+
const { index } = dto;
282+
const client: RedisClient =
283+
await this.databaseClientFactory.getOrCreateClient(clientMetadata);
284+
285+
await client.sendCommand(['FT.DROPINDEX', index], {
286+
replyEncoding: 'utf8',
287+
});
288+
289+
this.logger.debug(
290+
'Successfully deleted redisearch index ',
291+
clientMetadata,
292+
);
293+
} catch (error) {
294+
this.logger.error(
295+
'Failed to delete redisearch index ',
296+
error,
297+
clientMetadata,
298+
);
299+
300+
throw catchRedisSearchError(error);
301+
}
302+
}
303+
272304
/**
273305
* Get array of shards (client per each master node)
274306
* for STANDALONE will return array with a single shard

0 commit comments

Comments
 (0)