Skip to content

Commit d83e3bb

Browse files
Kristiyan IvanovArtemHoruzhenko
authored andcommitted
Feature/ri 7091 add an environment variable to skip the eula screen (#4588)
* RI-7091 - Add an environment variable to skip the EULA screen - updated privacy link approach * RI-7091 - Add an environment variable to skip the EULA screen - updated existing settings check * RI-7091 - Add an environment variable to skip the EULA screen - updated text - out of regular scope * RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery * RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery * RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery * RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery * RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery * RI-7091 - Add an environment variable to skip the EULA screen - fixed auto discovery * RI-7091 fix regular autodiscovery * RI-7091 - Add an environment variable to skip the EULA screen - testing a work around fix on top of Artem's suggestion * testing delaying of the autodiscovery as a way to avoid the odd race condition happening * removed setImmediate to check * removed setTimeouts * RI-7091 - extra logs and removed extra code * - * - * RI-7091 - Add an environment variable to skip the EULA screen - fixed integration tests * RI-7091 - Add an environment variable to skip the EULA screen - added BE tests * RI-7091 - Add an environment variable to skip the EULA screen - added FE tests --------- Co-authored-by: ArtemHoruzhenko <[email protected]>
1 parent 6d6982d commit d83e3bb

File tree

14 files changed

+228
-34
lines changed

14 files changed

+228
-34
lines changed

redisinsight/api/src/constants/agreements-spec.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"required": false,
88
"editable": true,
99
"disabled": false,
10+
"linkToPrivacyPolicy": true,
1011
"category": "privacy",
1112
"since": "1.0.1",
1213
"title": "Usage Data",
@@ -19,6 +20,7 @@
1920
"required": false,
2021
"editable": true,
2122
"disabled": false,
23+
"linkToPrivacyPolicy": false,
2224
"category": "notifications",
2325
"since": "1.0.6",
2426
"title": "Notification",
@@ -37,6 +39,7 @@
3739
"required": false,
3840
"editable": true,
3941
"disabled": false,
42+
"linkToPrivacyPolicy": false,
4043
"category": "privacy",
4144
"since": "1.0.3",
4245
"title": "Encryption",
@@ -49,6 +52,7 @@
4952
"required": false,
5053
"editable": true,
5154
"disabled": true,
55+
"linkToPrivacyPolicy": false,
5256
"category": "privacy",
5357
"since": "1.0.3",
5458
"title": "Encryption",
@@ -61,6 +65,7 @@
6165
"required": false,
6266
"editable": true,
6367
"disabled": true,
68+
"linkToPrivacyPolicy": false,
6469
"category": "privacy",
6570
"since": "1.0.5",
6671
"title": "Encryption",
@@ -75,6 +80,7 @@
7580
"required": true,
7681
"editable": false,
7782
"disabled": false,
83+
"linkToPrivacyPolicy": false,
7884
"since": "1.0.4",
7985
"title": "Server Side Public License",
8086
"label": "I have read and understood the Terms",

redisinsight/api/src/modules/database-discovery/local.database-discovery.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class LocalDatabaseDiscoveryService extends DatabaseDiscoveryService {
2626
firstRun?: boolean,
2727
): Promise<void> {
2828
try {
29-
// no need to auto discover for Redis Stack
29+
// No need to auto discover for Redis Stack - quick check
3030
if (SERVER_CONFIG.buildType === 'REDIS_STACK') {
3131
return;
3232
}

redisinsight/api/src/modules/encryption/encryption.service.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,44 @@ describe('EncryptionService', () => {
103103
});
104104
});
105105

106+
describe('isEncryptionAvailable', () => {
107+
it('should return true when multiple strategies are available (KEYTAR and PLAIN)', async () => {
108+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
109+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
110+
111+
const result = await service.isEncryptionAvailable();
112+
113+
expect(result).toBe(true);
114+
});
115+
116+
it('should return true when multiple strategies are available (KEY and PLAIN)', async () => {
117+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
118+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
119+
120+
const result = await service.isEncryptionAvailable();
121+
122+
expect(result).toBe(true);
123+
});
124+
125+
it('should return true when all strategies are available (KEY, KEYTAR and PLAIN)', async () => {
126+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
127+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
128+
129+
const result = await service.isEncryptionAvailable();
130+
131+
expect(result).toBe(true);
132+
});
133+
134+
it('should return false when only PLAIN strategy is available', async () => {
135+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
136+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
137+
138+
const result = await service.isEncryptionAvailable();
139+
140+
expect(result).toBe(false);
141+
});
142+
});
143+
106144
describe('getEncryptionStrategy', () => {
107145
it('Should return KEYTAR strategy based on app agreements', async () => {
108146
expect(await service.getEncryptionStrategy()).toEqual(

redisinsight/api/src/modules/init/local.init.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class LocalInitService extends InitService {
3232
await this.initAnalytics(firstStart);
3333
await this.featureService.recalculateFeatureFlags(sessionMetadata);
3434
await this.redisClientFactory.init();
35-
await this.databaseDiscoveryService.discover(sessionMetadata);
35+
await this.databaseDiscoveryService.discover(sessionMetadata, firstStart);
3636
}
3737

3838
async initAnalytics(firstStart: boolean) {

redisinsight/api/src/modules/settings/repositories/local.agreements.repository.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,54 @@ describe('LocalAgreementsRepository', () => {
5757
data: undefined,
5858
});
5959
});
60+
it('should create new agreements when entity exists but has no data', async () => {
61+
// Mock an entity that exists but has no data property
62+
const entityWithoutData = Object.assign(new AgreementsEntity(), {
63+
id: mockUserId,
64+
version: '1.0.0',
65+
data: undefined, // This should trigger the !entity?.data check
66+
});
67+
68+
repository.findOneBy.mockResolvedValueOnce(entityWithoutData);
69+
70+
const result = await service.getOrCreate(mockSessionMetadata);
71+
72+
// Verify that save was called to create a new entity
73+
expect(repository.save).toHaveBeenCalledWith({
74+
id: 1,
75+
data: undefined,
76+
});
77+
78+
expect(result).toEqual({
79+
...mockAgreements,
80+
version: undefined,
81+
data: undefined,
82+
});
83+
});
84+
it('should create new agreements when entity exists but has empty string data', async () => {
85+
// Mock an entity that exists but has empty string data
86+
const entityWithEmptyData = Object.assign(new AgreementsEntity(), {
87+
id: mockUserId,
88+
version: '1.0.0',
89+
data: '', // This should also trigger the !entity?.data check
90+
});
91+
92+
repository.findOneBy.mockResolvedValueOnce(entityWithEmptyData);
93+
94+
const result = await service.getOrCreate(mockSessionMetadata);
95+
96+
// Verify that save was called to create a new entity
97+
expect(repository.save).toHaveBeenCalledWith({
98+
id: 1,
99+
data: undefined,
100+
});
101+
102+
expect(result).toEqual({
103+
...mockAgreements,
104+
version: undefined,
105+
data: undefined,
106+
});
107+
});
60108
it('should fail to create with unique constraint and return existing', async () => {
61109
repository.findOneBy.mockResolvedValueOnce(null);
62110
repository.findOneBy.mockResolvedValueOnce(mockAgreements);

redisinsight/api/src/modules/settings/repositories/local.agreements.repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ export class LocalAgreementsRepository extends AgreementsRepository {
1717

1818
async getOrCreate(
1919
sessionMetadata: SessionMetadata,
20-
defaultOptions: DefaultAgreementsOptions = {}
20+
defaultOptions: DefaultAgreementsOptions = {},
2121
): Promise<Agreements> {
2222
let entity = await this.repository.findOneBy({});
23-
if (!entity) {
23+
if (!entity?.data) {
2424
try {
2525
entity = await this.repository.save(
2626
classToClass(AgreementsEntity, plainToInstance(Agreements, {

redisinsight/api/src/modules/settings/settings.service.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class SettingsService {
6464

6565
let defaultOptions: object;
6666
if (SERVER_CONFIG.acceptTermsAndConditions) {
67-
const isEncryptionAvailable = await this.encryptionService.isEncryptionAvailable();
67+
const isEncryptionAvailable = await this.encryptionService.isEncryptionAvailable();
6868

6969
defaultOptions = {
7070
data: {
@@ -84,7 +84,6 @@ export class SettingsService {
8484
sessionMetadata,
8585
);
8686

87-
8887
return classToClass(GetAppSettingsResponse, {
8988
...settings?.data,
9089
acceptTermsAndConditionsOverwritten: SERVER_CONFIG.acceptTermsAndConditions,

redisinsight/api/test/api/settings/GET-settings-agreements-spec.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const agreementItemSchema = Joi.object().keys({
1717
category: Joi.string().optional(),
1818
description: Joi.string().optional(),
1919
requiredText: Joi.string().optional(),
20+
linkToPrivacyPolicy: Joi.boolean().required(),
2021
});
2122

2223
const responseSchema = Joi.object()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import React from 'react'
2+
import { render, screen, fireEvent } from 'uiSrc/utils/test-utils'
3+
import ConsentOption from './ConsentOption'
4+
import { IConsent } from '../ConsentsSettings'
5+
6+
const mockConsent: IConsent = {
7+
agreementName: 'analytics',
8+
title: 'Analytics',
9+
label: 'Share usage data',
10+
description: 'Help us improve Redis Insight by sharing usage data.',
11+
required: false,
12+
editable: true,
13+
disabled: false,
14+
defaultValue: false,
15+
displayInSetting: true,
16+
since: '1.0.0',
17+
linkToPrivacyPolicy: false,
18+
}
19+
20+
const mockOnChangeAgreement = jest.fn()
21+
22+
const defaultProps = {
23+
consent: mockConsent,
24+
onChangeAgreement: mockOnChangeAgreement,
25+
checked: false,
26+
}
27+
28+
describe('ConsentOption', () => {
29+
beforeEach(() => {
30+
jest.clearAllMocks()
31+
})
32+
33+
it('should render', () => {
34+
expect(render(<ConsentOption {...defaultProps} />)).toBeTruthy()
35+
})
36+
37+
it('should render switch with correct test id', () => {
38+
render(<ConsentOption {...defaultProps} />)
39+
expect(screen.getByTestId('switch-option-analytics')).toBeInTheDocument()
40+
})
41+
42+
it('should call onChangeAgreement when switch is clicked', () => {
43+
render(<ConsentOption {...defaultProps} />)
44+
45+
fireEvent.click(screen.getByTestId('switch-option-analytics'))
46+
47+
expect(mockOnChangeAgreement).toHaveBeenCalledWith(true, 'analytics')
48+
})
49+
50+
it('should render description without privacy policy link when linkToPrivacyPolicy is false', () => {
51+
const consentWithDescription = {
52+
...mockConsent,
53+
description: 'Help us improve Redis Insight by sharing usage data.',
54+
linkToPrivacyPolicy: false,
55+
}
56+
57+
render(<ConsentOption {...defaultProps} consent={consentWithDescription} />)
58+
59+
expect(screen.getByText('Help us improve Redis Insight by sharing usage data.')).toBeInTheDocument()
60+
expect(screen.queryByText('Privacy Policy')).not.toBeInTheDocument()
61+
})
62+
63+
it('should render description with privacy policy link when linkToPrivacyPolicy is true', () => {
64+
const consentWithPrivacyLink = {
65+
...mockConsent,
66+
description: 'Help us improve Redis Insight by sharing usage data.',
67+
linkToPrivacyPolicy: true,
68+
}
69+
70+
render(<ConsentOption {...defaultProps} consent={consentWithPrivacyLink} />)
71+
72+
// Verify that the Privacy Policy link is rendered
73+
expect(screen.getByText('Privacy Policy')).toBeInTheDocument()
74+
75+
const privacyPolicyLink = screen.getByText('Privacy Policy')
76+
expect(privacyPolicyLink.closest('a')).toHaveAttribute(
77+
'href',
78+
'https://redis.io/legal/privacy-policy/?utm_source=redisinsight&utm_medium=app&utm_campaign=telemetry'
79+
)
80+
})
81+
82+
it('should render description with privacy policy link on settings page when linkToPrivacyPolicy is true', () => {
83+
const consentWithPrivacyLink = {
84+
...mockConsent,
85+
description: 'Help us improve Redis Insight by sharing usage data.',
86+
linkToPrivacyPolicy: true,
87+
}
88+
89+
render(<ConsentOption {...defaultProps} consent={consentWithPrivacyLink} isSettingsPage />)
90+
91+
// Verify that the Privacy Policy link is rendered
92+
expect(screen.getByText('Privacy Policy')).toBeInTheDocument()
93+
})
94+
95+
it('should not render privacy policy link on settings page when linkToPrivacyPolicy is false', () => {
96+
const consentWithoutPrivacyLink = {
97+
...mockConsent,
98+
description: 'Help us improve Redis Insight by sharing usage data.',
99+
linkToPrivacyPolicy: false,
100+
}
101+
102+
render(<ConsentOption {...defaultProps} consent={consentWithoutPrivacyLink} isSettingsPage />)
103+
104+
expect(screen.getByText('Help us improve Redis Insight by sharing usage data.')).toBeInTheDocument()
105+
expect(screen.queryByText('Privacy Policy')).not.toBeInTheDocument()
106+
})
107+
108+
it('should render disabled switch when consent is disabled', () => {
109+
const disabledConsent = {
110+
...mockConsent,
111+
disabled: true,
112+
}
113+
114+
render(<ConsentOption {...defaultProps} consent={disabledConsent} />)
115+
116+
const switchElement = screen.getByTestId('switch-option-analytics')
117+
expect(switchElement).toBeDisabled()
118+
})
119+
120+
it('should render checked switch when checked prop is true', () => {
121+
render(<ConsentOption {...defaultProps} checked />)
122+
123+
const switchElement = screen.getByTestId('switch-option-analytics')
124+
expect(switchElement).toBeChecked()
125+
})
126+
})

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ interface Props {
1515
checked: boolean
1616
isSettingsPage?: boolean
1717
withoutSpacer?: boolean
18-
linkToPrivacyPolicy?: boolean
1918
}
2019

2120
const ConsentOption = (props: Props) => {
@@ -25,7 +24,6 @@ const ConsentOption = (props: Props) => {
2524
checked,
2625
isSettingsPage = false,
2726
withoutSpacer = false,
28-
linkToPrivacyPolicy = false,
2927
} = props
3028

3129
return (
@@ -38,7 +36,7 @@ const ConsentOption = (props: Props) => {
3836
color="subdued"
3937
style={{ marginTop: '12px' }}
4038
>
41-
<ItemDescription description={consent.description} withLink={linkToPrivacyPolicy} />
39+
<ItemDescription description={consent.description} withLink={consent.linkToPrivacyPolicy} />
4240
</EuiText>
4341
<Spacer size="m" />
4442
</>
@@ -66,7 +64,7 @@ const ConsentOption = (props: Props) => {
6664
color="subdued"
6765
style={{ marginTop: '12px' }}
6866
>
69-
<ItemDescription description={consent.description} withLink={linkToPrivacyPolicy} />
67+
<ItemDescription description={consent.description} withLink={consent.linkToPrivacyPolicy} />
7068
</EuiText>
7169
)}
7270
</FlexItem>

0 commit comments

Comments
 (0)