Skip to content

Commit 6df022a

Browse files
author
Artem
committed
#RI-2935 BE Show Consumers List
1 parent 83b8e84 commit 6df022a

File tree

6 files changed

+196
-1
lines changed

6 files changed

+196
-1
lines changed

redisinsight/api/src/modules/browser/browser.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { StreamController } from 'src/modules/browser/controllers/stream/stream.
66
import { StreamService } from 'src/modules/browser/services/stream/stream.service';
77
import { ConsumerGroupController } from 'src/modules/browser/controllers/stream/consumer-group.controller';
88
import { ConsumerGroupService } from 'src/modules/browser/services/stream/consumer-group.service';
9+
import { ConsumerController } from 'src/modules/browser/controllers/stream/consumer.controller';
10+
import { ConsumerService } from 'src/modules/browser/services/stream/consumer.service';
911
import { HashController } from './controllers/hash/hash.controller';
1012
import { KeysController } from './controllers/keys/keys.controller';
1113
import { KeysBusinessService } from './services/keys-business/keys-business.service';
@@ -35,6 +37,7 @@ import { BrowserToolClusterService } from './services/browser-tool-cluster/brows
3537
HashController,
3638
StreamController,
3739
ConsumerGroupController,
40+
ConsumerController,
3841
],
3942
providers: [
4043
KeysBusinessService,
@@ -46,6 +49,7 @@ import { BrowserToolClusterService } from './services/browser-tool-cluster/brows
4649
HashBusinessService,
4750
StreamService,
4851
ConsumerGroupService,
52+
ConsumerService,
4953
BrowserToolService,
5054
BrowserToolClusterService,
5155
],
@@ -62,6 +66,9 @@ export class BrowserModule implements NestModule {
6266
RouterModule.resolvePath(SetController),
6367
RouterModule.resolvePath(ZSetController),
6468
RouterModule.resolvePath(RejsonRlController),
69+
RouterModule.resolvePath(StreamController),
70+
RouterModule.resolvePath(ConsumerGroupController),
71+
RouterModule.resolvePath(ConsumerController),
6572
);
6673
}
6774
}

redisinsight/api/src/modules/browser/constants/browser-tool-commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export enum BrowserToolStreamCommands {
8181
XAdd = 'xadd',
8282
XDel = 'xdel',
8383
XInfoGroups = 'xinfo groups',
84+
XInfoConsumers = 'xinfo consumers',
8485
XPending = 'xpending',
8586
XGroupCreate = 'xgroup create',
8687
XGroupSetId = 'xgroup setid',

redisinsight/api/src/modules/browser/controllers/stream/consumer-group.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class ConsumerGroupController {
2626

2727
@Post('/get')
2828
@ApiRedisInstanceOperation({
29-
description: 'Get stream entries',
29+
description: 'Get consumer groups list',
3030
statusCode: 200,
3131
responses: [
3232
{
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
Body,
3+
Controller,
4+
Param,
5+
Post,
6+
UsePipes,
7+
ValidationPipe,
8+
} from '@nestjs/common';
9+
import { ApiTags } from '@nestjs/swagger';
10+
import { ApiRedisInstanceOperation } from 'src/decorators/api-redis-instance-operation.decorator';
11+
import {
12+
ConsumerDto,
13+
ConsumerGroupDto,
14+
GetConsumersDto,
15+
} from 'src/modules/browser/dto/stream.dto';
16+
import { ConsumerService } from 'src/modules/browser/services/stream/consumer.service';
17+
18+
@ApiTags('Streams')
19+
@Controller('streams/consumer-groups/consumers')
20+
@UsePipes(new ValidationPipe({ transform: true }))
21+
export class ConsumerController {
22+
constructor(private service: ConsumerService) {}
23+
24+
@Post('/get')
25+
@ApiRedisInstanceOperation({
26+
description: 'Get stream entries',
27+
statusCode: 200,
28+
responses: [
29+
{
30+
status: 200,
31+
description: 'Returns stream consumer groups.',
32+
type: ConsumerGroupDto,
33+
isArray: true,
34+
},
35+
],
36+
})
37+
async getConsumers(
38+
@Param('dbInstance') instanceId: string,
39+
@Body() dto: GetConsumersDto,
40+
): Promise<ConsumerDto[]> {
41+
return this.service.getConsumers({ instanceId }, dto);
42+
}
43+
}

redisinsight/api/src/modules/browser/dto/stream.dto.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,38 @@ export class DeleteConsumerGroupsResponse {
289289
})
290290
affected: number;
291291
}
292+
293+
export class ConsumerDto {
294+
@ApiProperty({
295+
type: String,
296+
description: 'The consumer\'s name',
297+
example: 'consumer-1',
298+
})
299+
name: string;
300+
301+
@ApiProperty({
302+
type: Number,
303+
description: 'The number of pending messages for the client, '
304+
+ 'which are messages that were delivered but are yet to be acknowledged',
305+
example: 2,
306+
})
307+
pending: number = 0;
308+
309+
@ApiProperty({
310+
type: Number,
311+
description: 'The number of milliseconds that have passed since the consumer last interacted with the server',
312+
example: 22442,
313+
})
314+
idle: number = 0;
315+
}
316+
317+
export class GetConsumersDto extends KeyDto {
318+
@ApiProperty({
319+
type: String,
320+
description: 'Consumer group name',
321+
example: 'group-1',
322+
})
323+
@IsNotEmpty()
324+
@IsString()
325+
groupName: string;
326+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {
2+
BadRequestException, Injectable, Logger, NotFoundException,
3+
} from '@nestjs/common';
4+
import { IFindRedisClientInstanceByOptions } from 'src/modules/core/services/redis/redis.service';
5+
import { RedisErrorCodes } from 'src/constants';
6+
import { catchAclError, convertStringsArrayToObject } from 'src/utils';
7+
import {
8+
BrowserToolKeysCommands, BrowserToolStreamCommands,
9+
} from 'src/modules/browser/constants/browser-tool-commands';
10+
import { BrowserToolService } from 'src/modules/browser/services/browser-tool/browser-tool.service';
11+
import ERROR_MESSAGES from 'src/constants/error-messages';
12+
import {
13+
ConsumerDto,
14+
GetConsumersDto,
15+
} from 'src/modules/browser/dto/stream.dto';
16+
17+
@Injectable()
18+
export class ConsumerService {
19+
private logger = new Logger('ConsumerService');
20+
21+
constructor(private browserTool: BrowserToolService) {}
22+
23+
/**
24+
* Get consumers list inside particular group
25+
* @param clientOptions
26+
* @param dto
27+
*/
28+
async getConsumers(
29+
clientOptions: IFindRedisClientInstanceByOptions,
30+
dto: GetConsumersDto,
31+
): Promise<ConsumerDto[]> {
32+
try {
33+
this.logger.log('Getting consumers list.');
34+
35+
const exists = await this.browserTool.execCommand(
36+
clientOptions,
37+
BrowserToolKeysCommands.Exists,
38+
[dto.keyName],
39+
);
40+
41+
if (!exists) {
42+
return Promise.reject(new NotFoundException(ERROR_MESSAGES.KEY_NOT_EXIST));
43+
}
44+
45+
return ConsumerService.formatReplyToDto(await this.browserTool.execCommand(
46+
clientOptions,
47+
BrowserToolStreamCommands.XInfoConsumers,
48+
[dto.keyName, dto.groupName],
49+
));
50+
} catch (error) {
51+
if (error instanceof NotFoundException) {
52+
throw error;
53+
}
54+
55+
if (error?.message.includes(RedisErrorCodes.WrongType)) {
56+
throw new BadRequestException(error.message);
57+
}
58+
59+
throw catchAclError(error);
60+
}
61+
}
62+
63+
/**
64+
* Converts RESP response from Redis
65+
* [
66+
* ['name', 'consumer-1', 'pending', 0, 'idle', 258741],
67+
* ['name', 'consumer-2', 'pending', 0, 'idle', 258741],
68+
* ...
69+
* ]
70+
*
71+
* to DTO
72+
*
73+
* [
74+
* {
75+
* name: 'consumer-1',
76+
* pending: 0,
77+
* idle: 258741,
78+
* },
79+
* {
80+
* name: 'consumer-2',
81+
* pending: 0,
82+
* idle: 258741,
83+
* },
84+
* ...
85+
* ]
86+
* @param reply
87+
*/
88+
static formatReplyToDto(reply: Array<Array<string | number>>): ConsumerDto[] {
89+
return reply.map(ConsumerService.formatArrayToDto);
90+
}
91+
92+
/**
93+
* Format single reply entry to DTO
94+
* @param entry
95+
*/
96+
static formatArrayToDto(entry: Array<string | number>): ConsumerDto {
97+
if (!entry?.length) {
98+
return null;
99+
}
100+
101+
const entryObj = convertStringsArrayToObject(entry as string[]);
102+
103+
return {
104+
name: entryObj['name'],
105+
pending: entryObj['pending'],
106+
idle: entryObj['idle'],
107+
};
108+
}
109+
}

0 commit comments

Comments
 (0)