Skip to content

Commit b131809

Browse files
Merge pull request #64 from RedisInsight/main
New candidate release
2 parents ca67183 + 4d6299d commit b131809

File tree

15 files changed

+258
-69
lines changed

15 files changed

+258
-69
lines changed

redisinsight/api/src/__mocks__/analytics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const mockInstancesAnalyticsService = () => ({
2+
sendInstanceListReceivedEvent: jest.fn(),
23
sendInstanceAddedEvent: jest.fn(),
34
sendInstanceAddFailedEvent: jest.fn(),
45
sendInstanceEditedEvent: jest.fn(),

redisinsight/api/src/constants/commands/redistimeseries.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
"multiple": true,
123123
"optional": true
124124
}
125-
]
125+
],
126+
"since": "1.0.0",
127+
"group": "timeseries"
126128
}
127129
}

redisinsight/api/src/constants/telemetry-events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum TelemetryEvents {
1111
RedisInstanceDeleted = 'CONFIG_DATABASES_DATABASE_DELETED',
1212
RedisInstanceEditedByUser = 'CONFIG_DATABASES_DATABASE_EDITED_BY_USER',
1313
RedisInstanceConnectionFailed = 'DATABASE_CONNECTION_FAILED',
14+
RedisInstanceListReceived = 'CONFIG_DATABASES_DATABASE_LIST_DISPLAYED',
1415

1516
// Events for autodiscovery flows
1617
REClusterDiscoverySucceed = 'CONFIG_DATABASES_RE_CLUSTER_AUTODISCOVERY_SUCCEEDED',

redisinsight/api/src/modules/shared/services/instances-business/instances-analytics.service.spec.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,51 @@ describe('InstancesAnalytics', () => {
5353
);
5454
});
5555

56+
describe('sendInstanceListReceivedEvent', () => {
57+
const instance = mockDatabaseInstanceDto;
58+
it('should emit event with one db in the list', () => {
59+
service.sendInstanceListReceivedEvent([instance]);
60+
61+
expect(sendEventMethod).toHaveBeenCalledWith(
62+
TelemetryEvents.RedisInstanceListReceived,
63+
{
64+
numberOfDatabases: 1,
65+
},
66+
);
67+
});
68+
it('should emit event with several dbs in the list', () => {
69+
service.sendInstanceListReceivedEvent([instance, instance, instance]);
70+
71+
expect(sendEventMethod).toHaveBeenCalledWith(
72+
TelemetryEvents.RedisInstanceListReceived,
73+
{
74+
numberOfDatabases: 3,
75+
},
76+
);
77+
});
78+
it('should emit event with several empty in the list', () => {
79+
service.sendInstanceListReceivedEvent([]);
80+
81+
expect(sendEventMethod).toHaveBeenCalledWith(
82+
TelemetryEvents.RedisInstanceListReceived,
83+
{
84+
numberOfDatabases: 0,
85+
},
86+
);
87+
});
88+
it('should emit event with additional data', () => {
89+
service.sendInstanceListReceivedEvent([], { data: 'data' });
90+
91+
expect(sendEventMethod).toHaveBeenCalledWith(
92+
TelemetryEvents.RedisInstanceListReceived,
93+
{
94+
numberOfDatabases: 0,
95+
data: 'data',
96+
},
97+
);
98+
});
99+
});
100+
56101
describe('sendInstanceAddedEvent', () => {
57102
it('should emit event with enabled tls', () => {
58103
const instance = mockDatabaseInstanceDto;
@@ -72,6 +117,8 @@ describe('InstancesAnalytics', () => {
72117
numberOfKeysRange: '0 - 500 000',
73118
totalMemory: mockRedisGeneralInfo.usedMemory,
74119
numberedDatabases: mockRedisGeneralInfo.databases,
120+
numberOfModules: 0,
121+
modules: [],
75122
},
76123
);
77124
});
@@ -96,11 +143,13 @@ describe('InstancesAnalytics', () => {
96143
numberOfKeysRange: '0 - 500 000',
97144
totalMemory: mockRedisGeneralInfo.usedMemory,
98145
numberedDatabases: mockRedisGeneralInfo.databases,
146+
numberOfModules: 0,
147+
modules: [],
99148
},
100149
);
101150
});
102151
it('should emit event without additional info', () => {
103-
const instance = mockDatabaseInstanceDto;
152+
const instance = { ...mockDatabaseInstanceDto, modules: [{ name: 'search', version: 20000 }] };
104153
service.sendInstanceAddedEvent(instance, {
105154
version: mockRedisGeneralInfo.version,
106155
});
@@ -119,6 +168,8 @@ describe('InstancesAnalytics', () => {
119168
numberOfKeysRange: undefined,
120169
totalMemory: undefined,
121170
numberedDatabases: undefined,
171+
numberOfModules: 1,
172+
modules: [{ name: 'search', version: 20000 }],
122173
},
123174
);
124175
});

redisinsight/api/src/modules/shared/services/instances-business/instances-analytics.service.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ export class InstancesAnalyticsService extends TelemetryBaseService {
1212
super(eventEmitter);
1313
}
1414

15+
sendInstanceListReceivedEvent(
16+
instances: DatabaseInstanceResponse[],
17+
additionalData: object = {},
18+
): void {
19+
try {
20+
this.sendEvent(
21+
TelemetryEvents.RedisInstanceListReceived,
22+
{
23+
numberOfDatabases: instances.length,
24+
...additionalData,
25+
},
26+
);
27+
} catch (e) {
28+
// continue regardless of error
29+
}
30+
}
31+
1532
sendInstanceAddedEvent(
1633
instance: DatabaseInstanceResponse,
1734
additionalInfo: RedisDatabaseInfoResponse,
@@ -35,6 +52,8 @@ export class InstancesAnalyticsService extends TelemetryBaseService {
3552
numberOfKeysRange: getRangeForNumber(additionalInfo.totalKeys, TOTAL_KEYS_BREAKPOINTS),
3653
totalMemory: additionalInfo.usedMemory,
3754
numberedDatabases: additionalInfo.databases,
55+
numberOfModules: instance.modules.length,
56+
modules: instance.modules,
3857
},
3958
);
4059
} catch (e) {

redisinsight/api/src/modules/shared/services/instances-business/instances-business.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ export class InstancesBusinessService {
9292

9393
async getAll(): Promise<DatabaseInstanceResponse[]> {
9494
try {
95-
return (await this.databasesProvider.getAll()).map(convertEntityToDto);
95+
const result = (await this.databasesProvider.getAll()).map(convertEntityToDto);
96+
this.instancesAnalyticsService.sendInstanceListReceivedEvent(result);
97+
return result;
9698
} catch (error) {
9799
this.logger.error('Failed to get database instance list.', error);
98100
throw new InternalServerErrorException();

redisinsight/ui/src/components/consents-settings/ConsentsSettings.tsx

Lines changed: 66 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212
EuiToolTip,
1313
EuiForm,
1414
EuiHorizontalRule,
15+
EuiCallOut,
1516
} from '@elastic/eui'
17+
import cx from 'classnames'
1618
import parse from 'html-react-parser'
1719

1820
import { compareConsents } from 'uiSrc/utils'
@@ -149,34 +151,38 @@ const ConsentsSettings = ({ liveEditMode = false }: Props) => {
149151

150152
return (
151153
<EuiForm component="form" onSubmit={formik.handleSubmit} data-testid="consents-settings-form">
152-
{!!nonRequiredConsents.length && (
153-
<>
154-
<EuiSpacer size="s" />
155-
<EuiText size="s" color="subdued">
156-
To improve your experience, we use third party tools in RedisInsight. All data collected
157-
are completely anonymized, but we will not use these data for any purpose that you do
158-
not consent to.
159-
</EuiText>
160-
<EuiSpacer size="xl" />
161-
</>
162-
)}
163-
{
164-
nonRequiredConsents
165-
.map((consent: IConsent) => renderConsentOption(consent, nonRequiredConsents.length > 1))
166-
}
167-
168-
{!liveEditMode && (
169-
<>
170-
<EuiText size="s" color="subdued">
171-
While adding new plugins for Workbench, use files only from trusted authors
172-
to avoid automatic execution of malicious code.
173-
</EuiText>
174-
<EuiHorizontalRule margin="l" />
175-
</>
176-
)}
177-
154+
<div className={styles.consentsWrapper}>
155+
{!!nonRequiredConsents.length && (
156+
<>
157+
<EuiSpacer size="s" />
158+
<EuiText size="s" color="subdued">
159+
To improve your experience, we use third party tools in RedisInsight. All data collected
160+
are completely anonymized, but we will not use these data for any purpose that you do
161+
not consent to.
162+
</EuiText>
163+
<EuiSpacer size="xl" />
164+
</>
165+
)}
166+
{
167+
nonRequiredConsents
168+
.map((consent: IConsent) => renderConsentOption(consent, nonRequiredConsents.length > 1))
169+
}
170+
171+
{!liveEditMode && (
172+
<>
173+
<EuiCallOut>
174+
<EuiText size="s">
175+
While adding new visualization plugins, use files only from trusted authors
176+
to avoid automatic execution of malicious code.
177+
</EuiText>
178+
</EuiCallOut>
179+
<EuiHorizontalRule margin="l" className={cx({ [styles.pluginWarningHR]: !!requiredConsents.length })} />
180+
</>
181+
)}
182+
</div>
178183
{!!requiredConsents.length && (
179184
<>
185+
<EuiSpacer size="l" />
180186
<EuiText color="subdued" size="s">
181187
To use RedisInsight, please accept the terms and conditions:
182188
</EuiText>
@@ -185,39 +191,42 @@ const ConsentsSettings = ({ liveEditMode = false }: Props) => {
185191
)}
186192

187193
{requiredConsents.map((consent: IConsent) => renderConsentOption(consent))}
194+
188195
{!liveEditMode && (
189-
<EuiFlexGroup justifyContent="flexEnd" responsive={false}>
190-
<EuiSpacer size="l" />
191-
<EuiFlexItem grow={false}>
192-
<EuiToolTip
193-
position="top"
194-
anchorClassName="euiToolTip__btn-disabled"
195-
content={
196-
submitIsDisabled() ? (
197-
<span className="euiToolTip__content">
198-
{Object.values(errors).map((err) => [
199-
spec?.agreements[err as string]?.requiredText,
200-
<br key={err} />,
201-
])}
202-
</span>
203-
) : null
204-
}
205-
>
206-
<EuiButton
207-
fill
208-
color="secondary"
209-
className="btn-add"
210-
type="submit"
211-
onClick={() => {}}
212-
disabled={submitIsDisabled()}
213-
iconType={submitIsDisabled() ? 'iInCircle' : undefined}
214-
data-testid="btn-submit"
196+
<>
197+
{!requiredConsents.length && (<EuiSpacer size="l" />)}
198+
<EuiFlexGroup justifyContent="flexEnd" responsive={false}>
199+
<EuiFlexItem grow={false}>
200+
<EuiToolTip
201+
position="top"
202+
anchorClassName="euiToolTip__btn-disabled"
203+
content={
204+
submitIsDisabled() ? (
205+
<span className="euiToolTip__content">
206+
{Object.values(errors).map((err) => [
207+
spec?.agreements[err as string]?.requiredText,
208+
<br key={err} />,
209+
])}
210+
</span>
211+
) : null
212+
}
215213
>
216-
Submit
217-
</EuiButton>
218-
</EuiToolTip>
219-
</EuiFlexItem>
220-
</EuiFlexGroup>
214+
<EuiButton
215+
fill
216+
color="secondary"
217+
className="btn-add"
218+
type="submit"
219+
onClick={() => {}}
220+
disabled={submitIsDisabled()}
221+
iconType={submitIsDisabled() ? 'iInCircle' : undefined}
222+
data-testid="btn-submit"
223+
>
224+
Submit
225+
</EuiButton>
226+
</EuiToolTip>
227+
</EuiFlexItem>
228+
</EuiFlexGroup>
229+
</>
221230
)}
222231
</EuiForm>
223232
)

redisinsight/ui/src/components/consents-settings/ConsentsSettingsPopup/ConsentsSettingsPopup.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import {
88
EuiFlexGroup,
99
EuiFlexItem,
1010
EuiTitle,
11-
EuiSpacer,
12-
EuiText,
1311
} from '@elastic/eui'
1412

1513
import { Theme } from 'uiSrc/constants'
@@ -35,7 +33,12 @@ const ConsentsSettingsPopup = () => {
3533
<EuiOverlayMask>
3634
<EuiModal className={styles.consentsPopup} onClose={() => {}} data-testid="consents-settings-popup">
3735
<EuiModalHeader className={styles.modalHeader}>
38-
<EuiFlexGroup justifyContent="flexEnd">
36+
<EuiFlexGroup justifyContent="spaceBetween">
37+
<EuiFlexItem grow={false}>
38+
<EuiTitle size="s">
39+
<h3 className={styles.consentsPopupTitle}>EULA and Privacy Settings</h3>
40+
</EuiTitle>
41+
</EuiFlexItem>
3942
<EuiFlexItem grow={false}>
4043
<EuiIcon
4144
className={styles.redisIcon}
@@ -46,11 +49,6 @@ const ConsentsSettingsPopup = () => {
4649
</EuiFlexGroup>
4750
</EuiModalHeader>
4851
<EuiModalBody>
49-
<EuiTitle size="xs">
50-
<EuiText className={styles.consentsPopupTitle}>EULA and Privacy Settings</EuiText>
51-
</EuiTitle>
52-
<EuiSpacer size="xl" />
53-
5452
<ConsentsSettings />
5553
</EuiModalBody>
5654
</EuiModal>

redisinsight/ui/src/components/consents-settings/styles.module.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
@import '@elastic/eui/src/global_styling/mixins/helpers';
2+
@import '@elastic/eui/src/global_styling/index';
3+
14
.redisIcon {
25
width: 140px;
36
height: auto;
@@ -13,6 +16,9 @@
1316
.euiModal__closeIcon {
1417
display: none;
1518
}
19+
.euiModal__flex {
20+
max-height: 84vh !important;
21+
}
1622
}
1723

1824
a {
@@ -28,9 +34,21 @@
2834
padding-bottom: 4px;
2935
}
3036

37+
.consentsWrapper {
38+
@include euiScrollBar;
39+
overflow-x: hidden;
40+
overflow-y: auto;
41+
max-height: calc(84vh - 260px);
42+
}
43+
44+
.pluginWarningHR {
45+
margin-bottom: 0 !important;
46+
}
47+
3148
.consentsPopupTitle {
3249
color: var(--euiTextSubduedColorHover);
3350
font-weight: 500 !important;
51+
font-size: 18px !important;
3452
}
3553

3654
.switchOption {

redisinsight/ui/src/slices/app/redis-commands.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createSlice } from '@reduxjs/toolkit'
2-
import { uniqBy } from 'lodash'
2+
import { isString, uniqBy } from 'lodash'
33
import { apiService } from 'uiSrc/services'
44
import { ApiEndpoints, ICommand, ICommands } from 'uiSrc/constants'
55
import { getApiErrorMessage, isStatusSuccessful } from 'uiSrc/utils'
@@ -30,6 +30,7 @@ const appRedisCommandsSlice = createSlice({
3030
state.commandsArray = Object.keys(state.spec).sort()
3131
state.commandGroups = uniqBy(Object.values(payload), 'group')
3232
.map((item: ICommand) => item.group)
33+
.filter((group: string) => isString(group))
3334
},
3435
getRedisCommandsFailure: (state, { payload }) => {
3536
state.loading = false

0 commit comments

Comments
 (0)