Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
077369a
RI-6160 Add a list data type with multiple elements
KIvanow Oct 3, 2024
3d51076
RI-6160 fixed UI test
Oct 3, 2024
d49c34c
RI-6160 fixed api test
Oct 4, 2024
f64520c
RI-6160 Add a list data type with multiple elements - resovled tests
Oct 5, 2024
a9eb3e8
RI-6160 Add a list data type with multiple elements - reverted order …
Oct 5, 2024
3358f67
RI-6160 Add a list data type with multiple elements - reverted order …
Oct 5, 2024
cd1feb5
RI-6160 Add a list data type with multiple elements - reverted order …
Oct 5, 2024
0b1621d
fix for regression tests
mariasergeenko Oct 7, 2024
8e94a0d
add test for list keys
mariasergeenko Oct 8, 2024
c4b297f
fix db
mariasergeenko Oct 8, 2024
d54a280
remove skip
mariasergeenko Oct 8, 2024
166ede6
Merge pull request #3908 from RedisInsight/e2e/feature/RI-6160-Add-a-…
mariasergeenko Oct 8, 2024
1178771
RI-6186 The first item can't be removed
Oct 9, 2024
bb55928
RI-6187 Save and Cancel button are scrollable
Oct 9, 2024
07a4b6a
RI-6184 BROWSER_KEY_ADDED and TREE_VIEW_KEY_ADDED contain incorrect l…
Oct 9, 2024
8a29336
RI-6185 BROWSER_KEY_VALUE_ADDED and TREE_VIEW_KEY_VALUE_ADDED contain…
Oct 9, 2024
70b4c45
Merge branch 'feature/RI-6160-Add-a-list-data-type-with-multiple-elem…
Oct 9, 2024
bd6b58e
RI-6186 The first item can't be removed
Oct 9, 2024
41df850
RI-6186 The first item can't be removed
Oct 9, 2024
a2edfaf
RI-6188 There is no dropdown to specify how to add elements for addin…
Oct 10, 2024
f2be7b3
RI-6188 There is no dropdown to specify how to add elements for addin…
Oct 10, 2024
3081882
Merge branch 'main' into feature/RI-6160-Add-a-list-data-type-with-mu…
mariasergeenko Oct 11, 2024
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/browser/__mocks__/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const mockListElement2 = Buffer.from('Lorem ipsum dolor sit amet2.');
export const mockListElements = [mockListElement];
export const mockPushElementDto: PushElementToListDto = {
keyName: mockKeyDto.keyName,
element: mockListElement,
elements: mockListElements,
destination: ListElementDestination.Tail,
};
export const mockGetListElementsDto: GetListElementsDto = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KeyDto } from 'src/modules/browser/keys/dto';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsDefined, IsEnum } from 'class-validator';
import { IsArray, IsDefined, IsEnum } from 'class-validator';
import { IsRedisString, RedisStringType } from 'src/common/decorators';
import { RedisString } from 'src/common/constants';

Expand All @@ -11,13 +11,15 @@ export enum ListElementDestination {

export class PushElementToListDto extends KeyDto {
@ApiProperty({
description: 'List element',
description: 'List element(s)',
type: String,
isArray: true,
})
@IsDefined()
@IsRedisString()
@RedisStringType()
element: RedisString;
@IsArray()
@IsRedisString({ each: true })
@RedisStringType({ each: true })
elements: RedisString[];

@ApiPropertyOptional({
description:
Expand Down
34 changes: 25 additions & 9 deletions redisinsight/api/src/modules/browser/list/list.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('ListService', () => {
.calledWith([
BrowserToolListCommands.LPush,
mockPushElementDto.keyName,
mockPushElementDto.element,
...mockPushElementDto.elements,
])
.mockResolvedValue(1);

Expand All @@ -93,6 +93,22 @@ describe('ListService', () => {
).resolves.not.toThrow();
expect(service.createListWithExpiration).not.toHaveBeenCalled();
});

it('create list with expiration and push at the head', async () => {
when(mockStandaloneRedisClient.sendCommand)
.calledWith([
BrowserToolListCommands.LPush,
mockPushElementDto.keyName,
...mockPushElementDto.elements,
])
.mockResolvedValue(1);

await expect(
service.createList(mockBrowserClientMetadata, mockPushElementDto),
).resolves.not.toThrow();
expect(service.createListWithExpiration).not.toHaveBeenCalled();
});

it('key with this name exist', async () => {
when(mockStandaloneRedisClient.sendCommand)
.calledWith([BrowserToolKeysCommands.Exists, mockPushElementDto.keyName])
Expand Down Expand Up @@ -120,25 +136,25 @@ describe('ListService', () => {
});

describe('pushElement', () => {
it('succeed to insert element at the tail of the list data type', async () => {
it('succeed to insert element(s) at the tail of the list data type', async () => {
when(mockStandaloneRedisClient.sendCommand)
.calledWith([
BrowserToolListCommands.RPushX,
mockPushElementDto.keyName,
mockPushElementDto.element,
...mockPushElementDto.elements,
])
.mockResolvedValue(1);

await expect(
service.pushElement(mockBrowserClientMetadata, mockPushElementDto),
).resolves.not.toThrow();
});
it('succeed to insert element at the head of the list data type', async () => {
it('succeed to insert element(s) at the head of the list data type', async () => {
when(mockStandaloneRedisClient.sendCommand)
.calledWith([
BrowserToolListCommands.LPushX,
mockPushElementDto.keyName,
mockPushElementDto.element,
...mockPushElementDto.elements,
])
.mockResolvedValue(12);

Expand All @@ -154,7 +170,7 @@ describe('ListService', () => {
.calledWith([
BrowserToolListCommands.RPushX,
mockPushElementDto.keyName,
mockPushElementDto.element,
...mockPushElementDto.elements,
])
.mockResolvedValue(0);

Expand Down Expand Up @@ -475,7 +491,7 @@ describe('ListService', () => {
it("shouldn't throw error", async () => {
when(mockStandaloneRedisClient.sendPipeline)
.calledWith([
[BrowserToolListCommands.LPush, dto.keyName, dto.element],
[BrowserToolListCommands.RPush, dto.keyName, ...dto.elements],
[BrowserToolKeysCommands.Expire, dto.keyName, dto.expire],
])
.mockResolvedValue([
Expand All @@ -490,11 +506,11 @@ describe('ListService', () => {
it('should throw error', async () => {
const replyError: ReplyError = {
...mockRedisNoPermError,
command: 'LPUSH',
command: 'RPUSH',
};
when(mockStandaloneRedisClient.sendPipeline)
.calledWith([
[BrowserToolListCommands.LPush, dto.keyName, dto.element],
[BrowserToolListCommands.RPush, dto.keyName, ...dto.elements],
[BrowserToolKeysCommands.Expire, dto.keyName, dto.expire],
])
.mockResolvedValue([[replyError, []]]);
Expand Down
20 changes: 14 additions & 6 deletions redisinsight/api/src/modules/browser/list/list.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ export class ListService {
): Promise<PushListElementsResponse> {
try {
this.logger.log('Insert element at the tail/head of the list data type.');
const { keyName, element, destination } = dto;
const { keyName, elements, destination } = dto;
const client: RedisClient = await this.databaseClientFactory.getOrCreateClient(clientMetadata);

const total: RedisClientCommandReply = await client.sendCommand([
BrowserToolListCommands[destination === ListElementDestination.Tail ? 'RPushX' : 'LPushX'],
keyName,
element,
...elements,
]);
if (!total) {
this.logger.error(
Expand Down Expand Up @@ -234,17 +234,25 @@ export class ListService {
client: RedisClient,
dto: PushElementToListDto,
): Promise<void> {
const { keyName, element } = dto;
await client.sendCommand([BrowserToolListCommands.LPush, keyName, element]);
const { keyName, elements, destination } = dto;
await client.sendCommand([
BrowserToolListCommands[destination === ListElementDestination.Tail ? 'RPush' : 'LPush'],
keyName,
...elements
]);
}

public async createListWithExpiration(
client: RedisClient,
dto: CreateListWithExpireDto,
): Promise<void> {
const { keyName, element, expire } = dto;
const { keyName, elements, expire, destination } = dto;
const transactionResults = await client.sendPipeline([
[BrowserToolListCommands.LPush, keyName, element],
[
BrowserToolListCommands[destination === ListElementDestination.Tail ? 'RPush' : 'LPush'],
keyName,
...elements
],
[BrowserToolKeysCommands.Expire, keyName, expire],
]);
catchMultiTransactionError(transactionResults);
Expand Down
35 changes: 22 additions & 13 deletions redisinsight/api/test/api/list/POST-databases-id-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ const endpoint = (instanceId = constants.TEST_INSTANCE_ID) =>
// input data schema
const dataSchema = Joi.object({
keyName: Joi.string().allow('').required(),
element: Joi.string().required(),
elements: Joi.array().items(
Joi.custom((value, helpers) => {
if (typeof value === 'string' || Buffer.isBuffer(value)) {
return value;
}
return helpers.error('any.invalid');
}).messages({
'any.invalid': 'elements must be a string or a Buffer',
})
).required(),
expire: Joi.number().integer().allow(null).min(1).max(2147483647),
}).strict();

const validInputData = {
keyName: constants.TEST_LIST_KEY_1,
element: constants.TEST_LIST_ELEMENT_1,
elements: [constants.TEST_LIST_ELEMENT_1],
expire: constants.TEST_LIST_EXPIRE_1,
};

Expand Down Expand Up @@ -53,7 +62,7 @@ const createCheckFn = async (testCase) => {
} else {
if (testCase.statusCode === 201) {
expect(await rte.client.exists(testCase.data.keyName)).to.eql(1);
expect(await rte.client.lrange(testCase.data.keyName, 0, 100)).to.eql([testCase.data.element]);
expect(await rte.client.lrange(testCase.data.keyName, 0, 100)).to.eql(testCase.data.elements);
if (testCase.data.expire) {
expect(await rte.client.ttl(testCase.data.keyName)).to.gte(testCase.data.expire - 5);
} else {
Expand All @@ -74,7 +83,7 @@ describe('POST /databases/:databases/list', () => {
name: 'Should create list from buff',
data: {
keyName: constants.TEST_LIST_KEY_BIN_BUF_OBJ_1,
element: constants.TEST_LIST_ELEMENT_BIN_BUF_OBJ_1,
elements: [constants.TEST_LIST_ELEMENT_BIN_BUF_OBJ_1],
},
statusCode: 201,
after: async () => {
Expand All @@ -88,7 +97,7 @@ describe('POST /databases/:databases/list', () => {
name: 'Should create list from ascii',
data: {
keyName: constants.TEST_LIST_KEY_BIN_ASCII_1,
element: constants.TEST_LIST_ELEMENT_BIN_ASCII_1,
elements: [constants.TEST_LIST_ELEMENT_BIN_ASCII_1],
},
statusCode: 201,
after: async () => {
Expand Down Expand Up @@ -116,15 +125,15 @@ describe('POST /databases/:databases/list', () => {
name: 'Should create item with empty value',
data: {
keyName: constants.getRandomString(),
element: '',
elements: [''],
},
statusCode: 201,
},
{
name: 'Should create item with key ttl',
data: {
keyName: constants.getRandomString(),
element: constants.getRandomString(),
elements: [constants.getRandomString()],
expire: constants.TEST_STRING_EXPIRE_1,
},
statusCode: 201,
Expand All @@ -133,15 +142,15 @@ describe('POST /databases/:databases/list', () => {
name: 'Should create regular item',
data: {
keyName: constants.TEST_LIST_KEY_1,
element: constants.TEST_LIST_ELEMENT_1,
elements: [constants.TEST_LIST_ELEMENT_1],
},
statusCode: 201,
},
{
name: 'Should return conflict error if key already exists',
data: {
keyName: constants.TEST_LIST_KEY_1,
element: constants.getRandomString(),
elements: [constants.getRandomString()],
},
statusCode: 409,
responseBody: {
Expand All @@ -158,7 +167,7 @@ describe('POST /databases/:databases/list', () => {
endpoint: () => endpoint(constants.TEST_NOT_EXISTED_INSTANCE_ID),
data: {
keyName: constants.TEST_LIST_KEY_1,
element: constants.getRandomString(),
elements: [constants.getRandomString()],
},
statusCode: 404,
responseBody: {
Expand All @@ -183,7 +192,7 @@ describe('POST /databases/:databases/list', () => {
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
data: {
keyName: constants.getRandomString(),
element: constants.TEST_LIST_ELEMENT_1,
elements: [constants.TEST_LIST_ELEMENT_1],
},
statusCode: 201,
},
Expand All @@ -192,7 +201,7 @@ describe('POST /databases/:databases/list', () => {
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
data: {
keyName: constants.getRandomString(),
element: constants.getRandomString(),
elements: [constants.getRandomString()],
},
statusCode: 403,
responseBody: {
Expand All @@ -206,7 +215,7 @@ describe('POST /databases/:databases/list', () => {
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
data: {
keyName: constants.getRandomString(),
element: constants.getRandomString(),
elements: [constants.getRandomString()],
},
statusCode: 403,
responseBody: {
Expand Down
Loading