Skip to content

Commit 065741b

Browse files
authored
Merge pull request #1782 from RedisInsight/feature/RI-4186-Upload_custom_tutorials
Feature/ri 4186 upload custom tutorials
2 parents 53b4734 + 4b22c21 commit 065741b

File tree

110 files changed

+4956
-371
lines changed

Some content is hidden

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

110 files changed

+4956
-371
lines changed

redisinsight/api/config/default.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default {
2424
logs: join(homedir, 'logs'),
2525
defaultPlugins: join(staticDir, 'plugins'),
2626
customPlugins: join(homedir, 'plugins'),
27+
customTutorials: join(homedir, 'custom-tutorials'),
2728
pluginsAssets: join(staticDir, 'resources', 'plugins'),
2829
commands: join(homedir, 'commands'),
2930
defaultCommandsDir: join(defaultsDir, 'commands'),
@@ -45,6 +46,7 @@ export default {
4546
staticUri: '/static',
4647
guidesUri: '/static/guides',
4748
tutorialsUri: '/static/tutorials',
49+
customTutorialsUri: '/static/custom-tutorials',
4850
contentUri: '/static/content',
4951
defaultPluginsUri: '/static/plugins',
5052
pluginsAssetsUri: '/static/resources/plugins',
@@ -95,6 +97,7 @@ export default {
9597
},
9698
analytics: {
9799
writeKey: process.env.SEGMENT_WRITE_KEY || 'SOURCE_WRITE_KEY',
100+
flushInterval: parseInt(process.env.ANALYTICS_FLUSH_INTERVAL, 10) || 3000,
98101
},
99102
logger: {
100103
logLevel: process.env.LOG_LEVEL || 'info', // log level
@@ -108,14 +111,14 @@ export default {
108111
},
109112
guides: {
110113
updateUrl: process.env.GUIDES_UPDATE_URL
111-
|| 'https://github.com/RedisInsight/Guides/releases/download/release',
114+
|| 'https://github.com/RedisInsight/Guides/releases/download/2.x.x',
112115
zip: process.env.GUIDES_ZIP || dataZipFileName,
113116
buildInfo: process.env.GUIDES_CHECKSUM || buildInfoFileName,
114117
devMode: !!process.env.GUIDES_DEV_PATH,
115118
},
116119
tutorials: {
117120
updateUrl: process.env.TUTORIALS_UPDATE_URL
118-
|| 'https://github.com/RedisInsight/Tutorials/releases/download/release',
121+
|| 'https://github.com/RedisInsight/Tutorials/releases/download/2.x.x',
119122
zip: process.env.TUTORIALS_ZIP || dataZipFileName,
120123
buildInfo: process.env.TUTORIALS_CHECKSUM || buildInfoFileName,
121124
devMode: !!process.env.TUTORIALS_DEV_PATH,

redisinsight/api/config/ormconfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ClientCertificateEntity } from 'src/modules/certificate/entities/client
1212
import { DatabaseEntity } from 'src/modules/database/entities/database.entity';
1313
import { SshOptionsEntity } from 'src/modules/ssh/entities/ssh-options.entity';
1414
import { BrowserHistoryEntity } from 'src/modules/browser/entities/browser-history.entity';
15+
import { CustomTutorialEntity } from 'src/modules/custom-tutorial/entities/custom-tutorial.entity';
1516
import migrations from '../migration';
1617
import * as config from '../src/utils/config';
1718

@@ -35,6 +36,7 @@ const ormConfig = {
3536
DatabaseAnalysisEntity,
3637
BrowserHistoryEntity,
3738
SshOptionsEntity,
39+
CustomTutorialEntity,
3840
],
3941
migrations,
4042
};

redisinsight/api/config/production.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default {
1212
prevHomedir,
1313
logs: join(homedir, 'logs'),
1414
customPlugins: join(homedir, 'plugins'),
15+
customTutorials: join(homedir, 'custom-tutorials'),
1516
commands: join(homedir, 'commands'),
1617
guides: process.env.GUIDES_DEV_PATH || join(homedir, 'guides'),
1718
tutorials: process.env.TUTORIALS_DEV_PATH || join(homedir, 'tutorials'),
@@ -24,6 +25,7 @@ export default {
2425
},
2526
analytics: {
2627
writeKey: process.env.SEGMENT_WRITE_KEY || 'lK5MNZgHbxj6vQwFgqZxygA0BiDQb32n',
28+
flushInterval: parseInt(process.env.ANALYTICS_FLUSH_INTERVAL, 10) || 10000,
2729
},
2830
db: {
2931
database: join(homedir, 'redisinsight.db'),

redisinsight/api/config/staging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default {
1212
prevHomedir,
1313
logs: join(homedir, 'logs'),
1414
customPlugins: join(homedir, 'plugins'),
15+
customTutorials: join(homedir, 'custom-tutorials'),
1516
commands: join(homedir, 'commands'),
1617
guides: process.env.GUIDES_DEV_PATH || join(homedir, 'guides'),
1718
tutorials: process.env.TUTORIALS_DEV_PATH || join(homedir, 'tutorials'),
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class customTutorials1677135091633 implements MigrationInterface {
4+
name = 'customTutorials1677135091633'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`CREATE TABLE "custom_tutorials" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "link" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')))`);
8+
}
9+
10+
public async down(queryRunner: QueryRunner): Promise<void> {
11+
await queryRunner.query(`DROP TABLE "custom_tutorials"`);
12+
}
13+
14+
}

redisinsight/api/migration/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { browserHistory1674539211397 } from './1674539211397-browser-history';
2828
import { databaseAnalysisRecommendations1674660306971 } from './1674660306971-database-analysis-recommendations';
2929
import { databaseTimeout1675398140189 } from './1675398140189-database-timeout';
3030
import { databaseCompressor1678182722874 } from './1678182722874-database-compressor';
31+
import { customTutorials1677135091633 } from './1677135091633-custom-tutorials';
3132

3233
export default [
3334
initialMigration1614164490968,
@@ -60,4 +61,5 @@ export default [
6061
browserHistory1674539211397,
6162
databaseTimeout1675398140189,
6263
databaseCompressor1678182722874,
64+
customTutorials1677135091633,
6365
];

redisinsight/api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"lodash": "^4.17.20",
6666
"nest-router": "^1.0.9",
6767
"nest-winston": "^1.4.0",
68+
"nestjs-form-data": "^1.8.7",
6869
"node-version-compare": "^1.0.3",
6970
"reflect-metadata": "^0.1.13",
7071
"rxjs": "^7.5.6",
@@ -83,6 +84,7 @@
8384
"@nestjs/cli": "^9.1.2",
8485
"@nestjs/schematics": "^9.0.3",
8586
"@nestjs/testing": "^9.0.11",
87+
"@types/adm-zip": "^0.5.0",
8688
"@types/axios": "^0.14.0",
8789
"@types/express": "^4.17.3",
8890
"@types/jest": "^26.0.15",
@@ -108,6 +110,7 @@
108110
"mocha": "^8.4.0",
109111
"mocha-junit-reporter": "^2.0.0",
110112
"mocha-multi-reporters": "^1.5.1",
113+
"nock": "^13.3.0",
111114
"nyc": "^15.1.0",
112115
"object-diff": "^0.0.4",
113116
"rimraf": "^3.0.2",
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { CustomTutorial, CustomTutorialActions } from 'src/modules/custom-tutorial/models/custom-tutorial';
2+
import { CustomTutorialEntity } from 'src/modules/custom-tutorial/entities/custom-tutorial.entity';
3+
import { CustomTutorialManifestType } from 'src/modules/custom-tutorial/models/custom-tutorial.manifest';
4+
import { MemoryStoredFile } from 'nestjs-form-data';
5+
import { UploadCustomTutorialDto } from 'src/modules/custom-tutorial/dto/upload.custom-tutorial.dto';
6+
import AdmZip from 'adm-zip';
7+
8+
export const mockCustomTutorialId = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-ct-id';
9+
10+
export const mockCustomTutorialId2 = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-ct-id-2';
11+
12+
export const mockCustomTutorialTmpPath = '/tmp/path';
13+
14+
export const mockCustomTutorialsHttpLink = 'https://somesime.com/archive.zip';
15+
16+
export const mockCustomTutorial = Object.assign(new CustomTutorial(), {
17+
id: mockCustomTutorialId,
18+
name: 'custom tutorial',
19+
createdAt: new Date(),
20+
});
21+
22+
export const mockCustomTutorialEntity = Object.assign(new CustomTutorialEntity(), {
23+
...mockCustomTutorial,
24+
});
25+
26+
export const mockCustomTutorial2 = Object.assign(new CustomTutorial(), {
27+
id: mockCustomTutorialId2,
28+
name: 'custom tutorial 2',
29+
link: mockCustomTutorialsHttpLink,
30+
createdAt: new Date(),
31+
});
32+
33+
export const mockCustomTutorialZipFile = Object.assign(new MemoryStoredFile(), {
34+
size: 100,
35+
buffer: Buffer.from('zip-content', 'utf8'),
36+
});
37+
38+
export const mockCustomTutorialZipFileAxiosResponse = {
39+
data: mockCustomTutorialZipFile.buffer,
40+
};
41+
42+
export const mockCustomTutorialAdmZipEntry = {
43+
entryName: 'somefolder/info.md',
44+
} as AdmZip.IZipEntry;
45+
46+
export const mockCustomTutorialMacosxAdmZipEntry = {
47+
entryName: '__MACOSX/info.md',
48+
} as AdmZip.IZipEntry;
49+
50+
export const mockUploadCustomTutorialDto = Object.assign(new UploadCustomTutorialDto(), {
51+
file: mockCustomTutorialZipFile,
52+
});
53+
54+
export const mockUploadCustomTutorialExternalLinkDto = Object.assign(new UploadCustomTutorialDto(), {
55+
link: mockCustomTutorialsHttpLink,
56+
});
57+
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+
},
80+
},
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+
},
88+
},
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+
},
103+
},
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+
},
111+
},
112+
],
113+
},
114+
],
115+
},
116+
],
117+
};
118+
119+
export const mockCustomTutorialManifest = {
120+
...mockCustomTutorialManifestJson,
121+
type: CustomTutorialManifestType.Group,
122+
id: mockCustomTutorialId,
123+
label: mockCustomTutorial.name,
124+
_actions: mockCustomTutorial.actions,
125+
_path: mockCustomTutorial.path,
126+
};
127+
128+
export const mockCustomTutorialManifest2 = {
129+
type: CustomTutorialManifestType.Group,
130+
id: mockCustomTutorialId2,
131+
label: mockCustomTutorial2.name,
132+
_actions: mockCustomTutorial2.actions,
133+
_path: mockCustomTutorial2.path,
134+
children: [mockCustomTutorialManifestJson],
135+
};
136+
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,
145+
},
146+
children: [
147+
mockCustomTutorialManifest,
148+
mockCustomTutorialManifest2,
149+
],
150+
};
151+
152+
export const mockCustomTutorialFsProvider = jest.fn(() => ({
153+
unzipFromMemoryStoredFile: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
154+
unzipFromExternalLink: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
155+
unzipToTmpFolder: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
156+
moveFolder: jest.fn(),
157+
removeFolder: jest.fn(),
158+
}));
159+
160+
export const mockCustomTutorialManifestProvider = jest.fn(() => ({
161+
getOriginalManifestJson: jest.fn().mockResolvedValue(mockCustomTutorialManifestJson),
162+
getManifestJson: jest.fn().mockResolvedValue(mockCustomTutorialManifestJson),
163+
generateTutorialManifest: jest.fn().mockResolvedValue(mockCustomTutorialManifest),
164+
isOriginalManifestExists: jest.fn().mockResolvedValue(true),
165+
}));
166+
167+
export const mockCustomTutorialRepository = jest.fn(() => ({
168+
get: jest.fn().mockResolvedValue(mockCustomTutorial),
169+
create: jest.fn().mockResolvedValue(mockCustomTutorial),
170+
delete: jest.fn(),
171+
list: jest.fn().mockResolvedValue([
172+
mockCustomTutorial,
173+
mockCustomTutorial2,
174+
]),
175+
}));
176+
177+
export const mockCustomTutorialAnalytics = jest.fn(() => ({
178+
sendImportSucceeded: jest.fn(),
179+
sendImportFailed: jest.fn(),
180+
}));

redisinsight/api/src/__mocks__/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from './analytics';
1111
export * from './profiler';
1212
export * from './user';
1313
export * from './databases';
14+
export * from './custom-tutorial';
1415
export * from './autodiscovery';
1516
export * from './redis';
1617
export * from './server';

redisinsight/api/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CoreModule } from 'src/core.module';
2121
import { AutodiscoveryModule } from 'src/modules/autodiscovery/autodiscovery.module';
2222
import { DatabaseImportModule } from 'src/modules/database-import/database-import.module';
2323
import { DummyAuthMiddleware } from 'src/common/middlewares/dummy-auth.middleware';
24+
import { CustomTutorialModule } from 'src/modules/custom-tutorial/custom-tutorial.module';
2425
import { BrowserModule } from './modules/browser/browser.module';
2526
import { RedisEnterpriseModule } from './modules/redis-enterprise/redis-enterprise.module';
2627
import { RedisSentinelModule } from './modules/redis-sentinel/redis-sentinel.module';
@@ -53,6 +54,7 @@ const PATH_CONFIG = config.get('dir_path');
5354
NotificationModule,
5455
BulkActionsModule,
5556
ClusterMonitorModule,
57+
CustomTutorialModule.register(),
5658
DatabaseAnalysisModule,
5759
DatabaseImportModule,
5860
...(SERVER_CONFIG.staticContent

0 commit comments

Comments
 (0)