Skip to content

Commit 1205650

Browse files
committed
Merge branch 'feature/RI-4186-Upload_custom_tutorials' into fe/feature/I-4181_to-tutorial-button
2 parents 77743d8 + d5fb515 commit 1205650

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+931
-547
lines changed

redisinsight/api/config/default.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export default {
9797
},
9898
analytics: {
9999
writeKey: process.env.SEGMENT_WRITE_KEY || 'SOURCE_WRITE_KEY',
100+
flushInterval: parseInt(process.env.ANALYTICS_FLUSH_INTERVAL, 10) || 3000,
100101
},
101102
logger: {
102103
logLevel: process.env.LOG_LEVEL || 'info', // log level

redisinsight/api/config/production.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default {
2525
},
2626
analytics: {
2727
writeKey: process.env.SEGMENT_WRITE_KEY || 'lK5MNZgHbxj6vQwFgqZxygA0BiDQb32n',
28+
flushInterval: parseInt(process.env.ANALYTICS_FLUSH_INTERVAL, 10) || 10000,
2829
},
2930
db: {
3031
database: join(homedir, 'redisinsight.db'),

redisinsight/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
"mocha": "^8.4.0",
111111
"mocha-junit-reporter": "^2.0.0",
112112
"mocha-multi-reporters": "^1.5.1",
113+
"nock": "^13.3.0",
113114
"nyc": "^15.1.0",
114115
"object-diff": "^0.0.4",
115116
"rimraf": "^3.0.2",

redisinsight/api/src/__mocks__/custom-tutorial.ts

Lines changed: 81 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -48,116 +48,106 @@ export const mockCustomTutorialMacosxAdmZipEntry = {
4848
} as AdmZip.IZipEntry;
4949

5050
export const mockUploadCustomTutorialDto = Object.assign(new UploadCustomTutorialDto(), {
51-
name: mockCustomTutorial.name,
5251
file: mockCustomTutorialZipFile,
5352
});
5453

5554
export const mockUploadCustomTutorialExternalLinkDto = Object.assign(new UploadCustomTutorialDto(), {
56-
name: mockCustomTutorial.name,
5755
link: mockCustomTutorialsHttpLink,
5856
});
5957

60-
export const mockCustomTutorialManifestManifestJson = [
61-
{
62-
type: 'group',
63-
id: 'ct-folder-1',
64-
label: 'ct-folder-1',
65-
// args: {
66-
// withBorder: true,
67-
// initialIsOpen: true,
68-
// },
69-
children: [
70-
{
71-
type: CustomTutorialManifestType.Group,
72-
id: 'ct-sub-folder-1',
73-
label: 'ct-sub-folder-1',
74-
// args: {
75-
// initialIsOpen: false,
76-
// },
77-
children: [
78-
{
79-
type: CustomTutorialManifestType.InternalLink,
80-
id: 'introduction',
81-
label: 'introduction',
82-
args: {
83-
path: '/ct-folder-1/ct-sub-folder-1/introduction.md',
58+
export const mockCustomTutorialManifestJson = {
59+
type: CustomTutorialManifestType.Group,
60+
id: mockCustomTutorialId,
61+
label: mockCustomTutorial.name,
62+
children: [
63+
{
64+
type: 'group',
65+
id: 'ct-folder-1',
66+
label: 'ct-folder-1',
67+
children: [
68+
{
69+
type: CustomTutorialManifestType.Group,
70+
id: 'ct-sub-folder-1',
71+
label: 'ct-sub-folder-1',
72+
children: [
73+
{
74+
type: CustomTutorialManifestType.InternalLink,
75+
id: 'introduction',
76+
label: 'introduction',
77+
args: {
78+
path: '/ct-folder-1/ct-sub-folder-1/introduction.md',
79+
},
8480
},
85-
},
86-
{
87-
type: CustomTutorialManifestType.InternalLink,
88-
id: 'working-with-hashes',
89-
label: 'working-with-hashes',
90-
args: {
91-
path: '/ct-folder-1/ct-sub-folder-1/working-with-hashes.md',
81+
{
82+
type: CustomTutorialManifestType.InternalLink,
83+
id: 'working-with-hashes',
84+
label: 'working-with-hashes',
85+
args: {
86+
path: '/ct-folder-1/ct-sub-folder-1/working-with-hashes.md',
87+
},
9288
},
93-
},
94-
],
95-
},
96-
{
97-
type: CustomTutorialManifestType.Group,
98-
id: 'ct-sub-folder-2',
99-
label: 'ct-sub-folder-2',
100-
// args: {
101-
// withBorder: true,
102-
// initialIsOpen: false,
103-
// },
104-
children: [
105-
{
106-
type: CustomTutorialManifestType.InternalLink,
107-
id: 'introduction',
108-
label: 'introduction',
109-
args: {
110-
path: '/ct-folder-1/ct-sub-folder-2/introduction.md',
89+
],
90+
},
91+
{
92+
type: CustomTutorialManifestType.Group,
93+
id: 'ct-sub-folder-2',
94+
label: 'ct-sub-folder-2',
95+
children: [
96+
{
97+
type: CustomTutorialManifestType.InternalLink,
98+
id: 'introduction',
99+
label: 'introduction',
100+
args: {
101+
path: '/ct-folder-1/ct-sub-folder-2/introduction.md',
102+
},
111103
},
112-
},
113-
{
114-
type: CustomTutorialManifestType.InternalLink,
115-
id: 'working-with-graphs',
116-
label: 'working-with-graphs',
117-
args: {
118-
path: '/ct-folder-1/ct-sub-folder-2/working-with-graphs.md',
104+
{
105+
type: CustomTutorialManifestType.InternalLink,
106+
id: 'working-with-graphs',
107+
label: 'working-with-graphs',
108+
args: {
109+
path: '/ct-folder-1/ct-sub-folder-2/working-with-graphs.md',
110+
},
119111
},
120-
},
121-
],
122-
},
123-
],
124-
},
125-
];
112+
],
113+
},
114+
],
115+
},
116+
],
117+
};
126118

127-
export const mockCustomTutorialManifestManifest = {
119+
export const mockCustomTutorialManifest = {
120+
...mockCustomTutorialManifestJson,
128121
type: CustomTutorialManifestType.Group,
129122
id: mockCustomTutorialId,
130123
label: mockCustomTutorial.name,
131124
_actions: mockCustomTutorial.actions,
132125
_path: mockCustomTutorial.path,
133-
children: mockCustomTutorialManifestManifestJson,
134126
};
135127

136-
export const mockCustomTutorialManifestManifest2 = {
128+
export const mockCustomTutorialManifest2 = {
137129
type: CustomTutorialManifestType.Group,
138130
id: mockCustomTutorialId2,
139131
label: mockCustomTutorial2.name,
140132
_actions: mockCustomTutorial2.actions,
141133
_path: mockCustomTutorial2.path,
142-
children: mockCustomTutorialManifestManifestJson,
134+
children: [mockCustomTutorialManifestJson],
143135
};
144136

145-
export const globalCustomTutorialManifest = [
146-
{
147-
type: CustomTutorialManifestType.Group,
148-
id: 'custom-tutorials',
149-
label: 'MY TUTORIALS',
150-
_actions: [CustomTutorialActions.CREATE],
151-
args: {
152-
withBorder: true,
153-
initialIsOpen: true,
154-
},
155-
children: [
156-
mockCustomTutorialManifestManifest,
157-
mockCustomTutorialManifestManifest2,
158-
],
137+
export const globalCustomTutorialManifest = {
138+
type: CustomTutorialManifestType.Group,
139+
id: 'custom-tutorials',
140+
label: 'MY TUTORIALS',
141+
_actions: [CustomTutorialActions.CREATE],
142+
args: {
143+
withBorder: true,
144+
initialIsOpen: true,
159145
},
160-
];
146+
children: [
147+
mockCustomTutorialManifest,
148+
mockCustomTutorialManifest2,
149+
],
150+
};
161151

162152
export const mockCustomTutorialFsProvider = jest.fn(() => ({
163153
unzipFromMemoryStoredFile: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
@@ -168,8 +158,9 @@ export const mockCustomTutorialFsProvider = jest.fn(() => ({
168158
}));
169159

170160
export const mockCustomTutorialManifestProvider = jest.fn(() => ({
171-
getManifestJson: jest.fn().mockResolvedValue(mockCustomTutorialManifestManifestJson),
172-
generateTutorialManifest: jest.fn().mockResolvedValue(mockCustomTutorialManifestManifest),
161+
getOriginalManifestJson: jest.fn().mockResolvedValue(mockCustomTutorialManifestJson),
162+
getManifestJson: jest.fn().mockResolvedValue(mockCustomTutorialManifestJson),
163+
generateTutorialManifest: jest.fn().mockResolvedValue(mockCustomTutorialManifest),
173164
}));
174165

175166
export const mockCustomTutorialRepository = jest.fn(() => ({
@@ -181,3 +172,8 @@ export const mockCustomTutorialRepository = jest.fn(() => ({
181172
mockCustomTutorial2,
182173
]),
183174
}));
175+
176+
export const mockCustomTutorialAnalytics = jest.fn(() => ({
177+
sendImportSucceeded: jest.fn(),
178+
sendImportFailed: jest.fn(),
179+
}));

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export enum TelemetryEvents {
4444
WorkbenchCommandExecuted = 'WORKBENCH_COMMAND_EXECUTED',
4545
WorkbenchCommandErrorReceived = 'WORKBENCH_COMMAND_ERROR_RECEIVED',
4646
WorkbenchCommandDeleted = 'WORKBENCH_COMMAND_DELETE_COMMAND',
47+
// Custom tutorials
48+
WorkbenchEnablementAreaImportSucceeded = 'WORKBENCH_ENABLEMENT_AREA_IMPORT_SUCCEEDED',
49+
WorkbenchEnablementAreaImportFailed = 'WORKBENCH_ENABLEMENT_AREA_IMPORT_FAILED',
4750

4851
// Profiler
4952
ProfilerLogDownloaded = 'PROFILER_LOG_DOWNLOADED',

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export class AnalyticsService {
4545
this.sessionId = sessionId;
4646
this.anonymousId = anonymousId;
4747
this.appType = appType;
48-
this.analytics = new Analytics(ANALYTICS_CONFIG.writeKey);
48+
this.analytics = new Analytics(ANALYTICS_CONFIG.writeKey, {
49+
flushInterval: ANALYTICS_CONFIG.flushInterval,
50+
});
4951
}
5052

5153
@OnEvent(AppAnalyticsEvents.Track)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { EventEmitter2 } from '@nestjs/event-emitter';
3+
import { TelemetryEvents } from 'src/constants';
4+
import { CustomTutorialAnalytics } from 'src/modules/custom-tutorial/custom-tutorial.analytics';
5+
import { BadRequestException } from '@nestjs/common';
6+
7+
describe('CustomTutorialAnalytics', () => {
8+
let service: CustomTutorialAnalytics;
9+
let sendEventSpy;
10+
11+
beforeEach(async () => {
12+
jest.clearAllMocks();
13+
14+
const module: TestingModule = await Test.createTestingModule({
15+
providers: [
16+
EventEmitter2,
17+
CustomTutorialAnalytics,
18+
],
19+
}).compile();
20+
21+
service = await module.get(CustomTutorialAnalytics);
22+
sendEventSpy = jest.spyOn(service as any, 'sendEvent');
23+
});
24+
25+
describe('sendImportSucceeded', () => {
26+
it('should emit succeed event with manifest "yes"', () => {
27+
service.sendImportSucceeded({ manifest: true });
28+
29+
expect(sendEventSpy).toHaveBeenNthCalledWith(
30+
1,
31+
TelemetryEvents.WorkbenchEnablementAreaImportSucceeded,
32+
{
33+
manifest: 'yes',
34+
},
35+
);
36+
});
37+
it('should emit succeed event with manifest "no"', () => {
38+
service.sendImportSucceeded({ manifest: false });
39+
40+
expect(sendEventSpy).toHaveBeenNthCalledWith(
41+
1,
42+
TelemetryEvents.WorkbenchEnablementAreaImportSucceeded,
43+
{
44+
manifest: 'no',
45+
},
46+
);
47+
});
48+
});
49+
50+
describe('sendImportFailed', () => {
51+
it('should emit 1 event with "Error" cause', () => {
52+
service.sendImportFailed(new Error());
53+
54+
expect(sendEventSpy).toHaveBeenNthCalledWith(
55+
1,
56+
TelemetryEvents.WorkbenchEnablementAreaImportFailed,
57+
{
58+
error: 'Error',
59+
},
60+
);
61+
});
62+
it('should emit 1 event with "BadRequestException" cause', () => {
63+
service.sendImportFailed(new BadRequestException());
64+
65+
expect(sendEventSpy).toHaveBeenNthCalledWith(
66+
1,
67+
TelemetryEvents.WorkbenchEnablementAreaImportFailed,
68+
{
69+
error: 'BadRequestException',
70+
},
71+
);
72+
});
73+
});
74+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { TelemetryBaseService } from 'src/modules/analytics/telemetry.base.service';
3+
import { EventEmitter2 } from '@nestjs/event-emitter';
4+
import { TelemetryEvents } from 'src/constants';
5+
6+
@Injectable()
7+
export class CustomTutorialAnalytics extends TelemetryBaseService {
8+
constructor(protected eventEmitter: EventEmitter2) {
9+
super(eventEmitter);
10+
}
11+
12+
sendImportSucceeded(data: any = {}): void {
13+
this.sendEvent(
14+
TelemetryEvents.WorkbenchEnablementAreaImportSucceeded,
15+
{
16+
manifest: data?.manifest ? 'yes' : 'no',
17+
},
18+
);
19+
}
20+
21+
sendImportFailed(e: Error): void {
22+
this.sendEvent(
23+
TelemetryEvents.WorkbenchEnablementAreaImportFailed,
24+
{
25+
error: e?.constructor?.name || 'UncaughtError',
26+
},
27+
);
28+
}
29+
}

redisinsight/api/src/modules/custom-tutorial/custom-tutorial.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ export class CustomTutorialController {
6060
},
6161
],
6262
})
63-
async getGlobalManifest(): Promise<RootCustomTutorialManifest[]> {
64-
return this.service.getGlobalManifest();
63+
async getGlobalManifest(): Promise<RootCustomTutorialManifest> {
64+
return await this.service.getGlobalManifest();
6565
}
6666

6767
@Delete('/:id')

redisinsight/api/src/modules/custom-tutorial/custom-tutorial.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CustomTutorialRepository } from 'src/modules/custom-tutorial/repositori
99
import {
1010
LocalCustomTutorialRepository,
1111
} from 'src/modules/custom-tutorial/repositories/local.custom-tutorial.repository';
12+
import { CustomTutorialAnalytics } from 'src/modules/custom-tutorial/custom-tutorial.analytics';
1213

1314
@Module({})
1415
export class CustomTutorialModule {
@@ -22,6 +23,7 @@ export class CustomTutorialModule {
2223
CustomTutorialService,
2324
CustomTutorialFsProvider,
2425
CustomTutorialManifestProvider,
26+
CustomTutorialAnalytics,
2527
{
2628
provide: CustomTutorialRepository,
2729
useClass: repository,

0 commit comments

Comments
 (0)