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
1 change: 1 addition & 0 deletions redisinsight/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"body-parser": "^1.19.0",
"class-transformer": "^0.2.3",
"class-validator": "^0.12.2",
"date-fns": "^2.29.3",
"dotenv": "^16.0.0",
"express": "^4.17.1",
"fs-extra": "^10.0.0",
Expand Down
3 changes: 2 additions & 1 deletion redisinsight/api/src/constants/regex.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const ARG_IN_QUOTATION_MARKS_REGEX = /"[^"]*|'[^']*'|"+/g;
export const IS_INTEGER_NUMBER_REGEX = /^\d+$/;
export const IS_NUMBER_REGEX = /^-?\d*(\.\d+)?$/;
export const IS_NON_PRINTABLE_ASCII_CHARACTER = /[^ -~\u0007\b\t\n\r]/;
export const IP_ADDRESS_REGEX = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
export const PRIVATE_IP_ADDRESS_REGEX = /(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/;
export const IS_TIMESTAMP = /^\d{10,}$/;
export const IS_TIMESTAMP = /^(\d{10}|\d{13}|\d{16}|\d{19})$/;
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,39 @@ const mockRedisAclListResponse_2: string[] = [
const mockFTListResponse_1 = [];
const mockFTListResponse_2 = ['idx'];

const generateRTSRecommendationTests = [
{ input: ['0', ['123', 123]], expected: null },
{ input: ['0', ['1234567891', 3]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['1234567891', 1234567891]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['123', 1234567891]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['123', 12345678911]], expected: null },
{ input: ['0', ['123', 1234567891234]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['123', 12345678912345]], expected: null },
{ input: ['0', ['123', 1234567891234567]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['12345678912345678', 1]], expected: null },
{ input: ['0', ['1234567891234567891', 1]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['1', 1234567891.2]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['1234567891.2', 1]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['1234567891:12', 1]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['1234567891a12', 1]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['1234567891.2.2', 1]], expected: null },
{ input: ['0', ['1234567891asd', 1]], expected: null },
{ input: ['0', ['10-10-2020', 1]], expected: { name: RECOMMENDATION_NAMES.RTS } },
{ input: ['0', ['', 1]], expected: null },
{ input: ['0', ['1', -12]], expected: null },
{ input: ['0', ['1', -1234567891]], expected: null },
{ input: ['0', ['1', -1234567891.123]], expected: null },
{ input: ['0', ['1', -1234567891.123]], expected: null },
{ input: ['0', ['1234567891.-123', 1]], expected: null },
];

const mockZScanResponse_1 = [
'0',
[123456789, 123456789, 12345678910, 12345678910],
['1', 1, '12345678910', 12345678910],
];
const mockZScanResponse_2 = [
'0',
[12345678910, 12345678910, 1, 1],
['12345678910', 12345678910, 1, 1],
];

const mockKeys = [
Expand Down Expand Up @@ -489,28 +515,14 @@ describe('RecommendationProvider', () => {
});

describe('determineRTSRecommendation', () => {
it('should not return RTS recommendation', async () => {
test.each(generateRTSRecommendationTests)('%j', async ({ input, expected }) => {
when(nodeClient.sendCommand)
.calledWith(jasmine.objectContaining({ name: 'zscan' }))
.mockResolvedValue(mockZScanResponse_1);
.mockResolvedValue(input);

const RTSRecommendation = await service
.determineRTSRecommendation(nodeClient, mockKeys);
expect(RTSRecommendation).toEqual(null);
});

it('should return RTS recommendation', async () => {
when(nodeClient.sendCommand)
.calledWith(jasmine.objectContaining({ name: 'zscan' }))
.mockResolvedValueOnce(mockZScanResponse_1);

when(nodeClient.sendCommand)
.calledWith(jasmine.objectContaining({ name: 'zscan' }))
.mockResolvedValue(mockZScanResponse_2);

const RTSRecommendation = await service
.determineRTSRecommendation(nodeClient, mockSortedSets);
expect(RTSRecommendation).toEqual({ name: RECOMMENDATION_NAMES.RTS });
expect(RTSRecommendation).toEqual(expected);
});

it('should not return RTS recommendation when only 101 sorted set contain timestamp', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Injectable, Logger } from '@nestjs/common';
import { Redis, Cluster, Command } from 'ioredis';
import { get } from 'lodash';
import { get, isNumber } from 'lodash';
import { isValid } from 'date-fns';
import * as semverCompare from 'node-version-compare';
import { convertRedisInfoReplyToObject, convertBulkStringsToObject, convertStringsArrayToObject } from 'src/utils';
import { RECOMMENDATION_NAMES, IS_TIMESTAMP } from 'src/constants';
import { convertRedisInfoReplyToObject, convertBulkStringsToObject } from 'src/utils';
import {
RECOMMENDATION_NAMES, IS_TIMESTAMP, IS_INTEGER_NUMBER_REGEX, IS_NUMBER_REGEX,
} from 'src/constants';
import { RedisDataType } from 'src/modules/browser/dto';
import { Recommendation } from 'src/modules/database-analysis/models/recommendation';
import { Key } from 'src/modules/database-analysis/models';
Expand Down Expand Up @@ -311,8 +314,7 @@ export class RecommendationProvider {
// get first member-score pair
new Command('zscan', [keys[processedKeysNumber].name, '0', 'COUNT', 2], { replyEncoding: 'utf8' }),
) as string[];
// check is pair member-score is timestamp
if (IS_TIMESTAMP.test(membersArray[0]) && IS_TIMESTAMP.test(membersArray[1])) {
if (this.checkTimestamp(membersArray[0]) || this.checkTimestamp(membersArray[1])) {
isTimeSeries = true;
}
processedKeysNumber += 1;
Expand Down Expand Up @@ -421,4 +423,25 @@ export class RecommendationProvider {
}
return false;
}

private checkTimestamp(value: string): boolean {
try {
if (!IS_NUMBER_REGEX.test(value) && isValid(new Date(value))) {
return true;
}
const integerPart = parseInt(value, 10);
if (!IS_TIMESTAMP.test(integerPart.toString())) {
return false;
}
if (isNumber(value) || integerPart.toString().length === value.length) {
return true;
}
// check part after separator
const subPart = value.replace(integerPart.toString(), '');
return IS_INTEGER_NUMBER_REGEX.test(subPart.substring(1, subPart.length));
} catch (err) {
// ignore errors
return false;
}
}
}
5 changes: 5 additions & 0 deletions redisinsight/api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,11 @@ date-fns@^2.28.0:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.2.tgz#0d4b3d0f3dff0f920820a070920f0d9662c51931"
integrity sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA==

date-fns@^2.29.3:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==

debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
Expand Down