@crowdin/app-project-module 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/app-test/integration/index.js +3 -3
- package/out/app-test/integration/mocks/mock-axios.js +5 -4
- package/out/app-test/integration/mocks/mock-crowdin-api-client.js +6 -6
- package/out/app-test/integration/update-crowdin.js +5 -5
- package/out/app-test/integration/update-integration.js +5 -5
- package/out/modules/integration/handlers/user-error-detail.d.ts +2 -0
- package/out/modules/integration/handlers/user-error-detail.js +48 -0
- package/out/modules/integration/handlers/user-errors.d.ts +6 -0
- package/out/modules/integration/handlers/user-errors.js +39 -17
- package/out/modules/integration/index.js +8 -0
- package/out/static/ui/error.bundle.js +286 -91
- package/out/static/ui/error.bundle.js.map +1 -1
- package/out/static/ui/form.bundle.js +398 -289
- package/out/static/ui/form.bundle.js.map +1 -1
- package/out/static/ui/login.bundle.js +351 -121
- package/out/static/ui/login.bundle.js.map +1 -1
- package/out/static/ui/main.bundle.js +832 -386
- package/out/static/ui/main.bundle.js.map +1 -1
- package/out/static/ui/subscription.bundle.js +277 -88
- package/out/static/ui/subscription.bundle.js.map +1 -1
- package/out/storage/drizzle/d1/index.js +2 -1
- package/out/storage/drizzle/d1/migrations/user-errors-indexes.d.ts +7 -0
- package/out/storage/drizzle/d1/migrations/user-errors-indexes.js +27 -0
- package/out/storage/drizzle/index.d.ts +8 -1
- package/out/storage/drizzle/index.js +75 -4
- package/out/storage/drizzle/postgresql/config.d.ts +3 -2
- package/out/storage/drizzle/postgresql/config.js +2 -1
- package/out/storage/drizzle/postgresql/migrations/0003_user_errors_pagination_index.sql +5 -0
- package/out/storage/drizzle/postgresql/migrations/meta/0003_snapshot.json +646 -0
- package/out/storage/drizzle/postgresql/migrations/meta/_journal.json +7 -0
- package/out/storage/drizzle/sqlite/config.d.ts +3 -2
- package/out/storage/drizzle/sqlite/config.js +2 -1
- package/out/storage/drizzle/sqlite/migrations/0002_user_errors_indexes.sql +4 -0
- package/out/storage/drizzle/sqlite/migrations/meta/0002_snapshot.json +687 -0
- package/out/storage/drizzle/sqlite/migrations/meta/_journal.json +7 -0
- package/out/storage/types.d.ts +30 -0
- package/package.json +12 -11
|
@@ -21,15 +21,15 @@ exports.mockProjectId = 1;
|
|
|
21
21
|
exports.mockStoragesById = {};
|
|
22
22
|
exports.mockFiles = [];
|
|
23
23
|
exports.mockDirectories = [];
|
|
24
|
-
jest.mock('axios', () => mock_axios_1.mockAxios);
|
|
25
|
-
jest.mock('@crowdin/crowdin-api-client', () => jest.fn().mockImplementation(() => mock_crowdin_api_client_1.mockCrowdinApiClient));
|
|
24
|
+
globals_1.jest.mock('axios', () => mock_axios_1.mockAxios);
|
|
25
|
+
globals_1.jest.mock('@crowdin/crowdin-api-client', () => globals_1.jest.fn().mockImplementation(() => mock_crowdin_api_client_1.mockCrowdinApiClient));
|
|
26
26
|
const runProjectIntegrationTests = ({ getAppConfig, integrationTestConfig, }) => {
|
|
27
27
|
(0, globals_1.describe)('Run integration module tests', () => {
|
|
28
28
|
var _a, _b, _c;
|
|
29
29
|
if (!((_a = integrationTestConfig.getIntegrationFiles) === null || _a === void 0 ? void 0 : _a.expected.length)) {
|
|
30
30
|
throw new Error('Please provide a valid expected files');
|
|
31
31
|
}
|
|
32
|
-
beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
+
(0, globals_1.beforeAll)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
33
|
if (integrationTestConfig === null || integrationTestConfig === void 0 ? void 0 : integrationTestConfig.beforeAll) {
|
|
34
34
|
yield integrationTestConfig.beforeAll();
|
|
35
35
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mockAxios = void 0;
|
|
4
|
+
const globals_1 = require("@jest/globals");
|
|
4
5
|
const index_1 = require("../index");
|
|
5
|
-
const mockOriginalAxios = jest.requireActual('axios');
|
|
6
|
-
const mock = Object.assign(Object.assign({}, mockOriginalAxios), { get: jest.fn((url) => {
|
|
7
|
-
expect(url).toMatch(/^file-id:(\d+)$/);
|
|
6
|
+
const mockOriginalAxios = globals_1.jest.requireActual('axios');
|
|
7
|
+
const mock = Object.assign(Object.assign({}, mockOriginalAxios), { get: globals_1.jest.fn((url) => {
|
|
8
|
+
(0, globals_1.expect)(url).toMatch(/^file-id:(\d+)$/);
|
|
8
9
|
const fileId = Number(url.replace('file-id:', ''));
|
|
9
10
|
const file = index_1.mockFiles.find(({ id }) => id === fileId);
|
|
10
11
|
const storageId = file === null || file === void 0 ? void 0 : file.storageId;
|
|
11
|
-
expect(storageId).not.toBeUndefined();
|
|
12
|
+
(0, globals_1.expect)(storageId).not.toBeUndefined();
|
|
12
13
|
return {
|
|
13
14
|
// @ts-ignore
|
|
14
15
|
data: index_1.mockStoragesById[storageId].data,
|
|
@@ -57,7 +57,7 @@ const sleep = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
57
57
|
}, Math.floor(Math.random() * (max - min) + min));
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
|
-
const mockBuildProjectFileTranslation = jest.fn((projectId, fileId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
const mockBuildProjectFileTranslation = globals_1.jest.fn((projectId, fileId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
61
|
(0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
|
|
62
62
|
yield sleep();
|
|
63
63
|
return {
|
|
@@ -98,17 +98,17 @@ const mockProjectsGroupsApi = {
|
|
|
98
98
|
return getProjectResponse;
|
|
99
99
|
},
|
|
100
100
|
};
|
|
101
|
-
const mockCreateDirectory = jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
|
|
101
|
+
const mockCreateDirectory = globals_1.jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
102
|
(0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
|
|
103
103
|
yield sleep();
|
|
104
104
|
return crowdin_storage_1.crowdinFolders.create(params);
|
|
105
105
|
}));
|
|
106
|
-
const mockCreateFile = jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
|
|
106
|
+
const mockCreateFile = globals_1.jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
|
|
107
107
|
(0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
|
|
108
108
|
yield sleep();
|
|
109
109
|
return crowdin_storage_1.crowdinFiles.create(params);
|
|
110
110
|
}));
|
|
111
|
-
const mockListProjectDirectories = jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
|
+
const mockListProjectDirectories = globals_1.jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
112
|
(0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
|
|
113
113
|
return {
|
|
114
114
|
// data: options ? [] : mockDirectories.map((d: SourceFilesModel.Directory) => ({ data: d })),
|
|
@@ -119,7 +119,7 @@ const mockListProjectDirectories = jest.fn((projectId) => __awaiter(void 0, void
|
|
|
119
119
|
},
|
|
120
120
|
};
|
|
121
121
|
}));
|
|
122
|
-
const mockListProjectFiles = jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
122
|
+
const mockListProjectFiles = globals_1.jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
123
|
(0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
|
|
124
124
|
return {
|
|
125
125
|
data: index_1.mockFiles.map((d) => ({ data: d })),
|
|
@@ -129,7 +129,7 @@ const mockListProjectFiles = jest.fn((projectId) => __awaiter(void 0, void 0, vo
|
|
|
129
129
|
},
|
|
130
130
|
};
|
|
131
131
|
}));
|
|
132
|
-
const mockOriginalClient = jest.requireActual('@crowdin/crowdin-api-client');
|
|
132
|
+
const mockOriginalClient = globals_1.jest.requireActual('@crowdin/crowdin-api-client');
|
|
133
133
|
exports.mockCrowdinApiClient = Object.assign(Object.assign({}, mockOriginalClient), { sourceFilesApi: {
|
|
134
134
|
withFetchAll: () => {
|
|
135
135
|
return {
|
|
@@ -72,7 +72,7 @@ const updateCrowdinTest = (_a) => __awaiter(void 0, [_a], void 0, function* ({ a
|
|
|
72
72
|
}
|
|
73
73
|
const client = new crowdin_api_client_1.default({ token: 'fakeToken' });
|
|
74
74
|
const request = getFilesFromTree(expectedTree);
|
|
75
|
-
const updateProgressMock = jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
const updateProgressMock = globals_1.jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
76
|
return { isCanceled: false };
|
|
77
77
|
}));
|
|
78
78
|
const rootFolder = (0, crowdin_storage_1.getRootFolder)(appConfig);
|
|
@@ -106,10 +106,10 @@ const updateCrowdinTest = (_a) => __awaiter(void 0, [_a], void 0, function* ({ a
|
|
|
106
106
|
},
|
|
107
107
|
update: updateProgressMock,
|
|
108
108
|
type: types_1.JobClientType.MANUAL,
|
|
109
|
-
fetchTranslation: jest.fn(),
|
|
110
|
-
translationUploaded: jest.fn(),
|
|
111
|
-
markFilesAsUnsynced: jest.fn(),
|
|
112
|
-
unmarkFilesAsUnsynced: jest.fn(),
|
|
109
|
+
fetchTranslation: globals_1.jest.fn(),
|
|
110
|
+
translationUploaded: globals_1.jest.fn(),
|
|
111
|
+
markFilesAsUnsynced: globals_1.jest.fn(),
|
|
112
|
+
unmarkFilesAsUnsynced: globals_1.jest.fn(),
|
|
113
113
|
},
|
|
114
114
|
});
|
|
115
115
|
}, 'Fail to run method updateCrowdin()');
|
|
@@ -33,7 +33,7 @@ const updateIntegrationTest = (_a) => __awaiter(void 0, [_a], void 0, function*
|
|
|
33
33
|
}
|
|
34
34
|
const { updateIntegration, settings, integrationCredentials } = integrationTestConfig;
|
|
35
35
|
const client = new crowdin_api_client_1.default({ token: 'fakeToken' });
|
|
36
|
-
const updateProgressMock = jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
|
+
const updateProgressMock = globals_1.jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
37
|
return { isCanceled: false };
|
|
38
38
|
}));
|
|
39
39
|
const request = (0, exports.getUpdateIntegrationRequest)();
|
|
@@ -63,10 +63,10 @@ const updateIntegrationTest = (_a) => __awaiter(void 0, [_a], void 0, function*
|
|
|
63
63
|
},
|
|
64
64
|
update: updateProgressMock,
|
|
65
65
|
type: types_1.JobClientType.MANUAL,
|
|
66
|
-
fetchTranslation: jest.fn(),
|
|
67
|
-
translationUploaded: jest.fn(),
|
|
68
|
-
markFilesAsUnsynced: jest.fn(),
|
|
69
|
-
unmarkFilesAsUnsynced: jest.fn(),
|
|
66
|
+
fetchTranslation: globals_1.jest.fn(),
|
|
67
|
+
translationUploaded: globals_1.jest.fn(),
|
|
68
|
+
markFilesAsUnsynced: globals_1.jest.fn(),
|
|
69
|
+
unmarkFilesAsUnsynced: globals_1.jest.fn(),
|
|
70
70
|
},
|
|
71
71
|
});
|
|
72
72
|
}, 'Fail to run method updateIntegration()');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.default = handle;
|
|
13
|
+
const storage_1 = require("../../../storage");
|
|
14
|
+
const util_1 = require("../../../util");
|
|
15
|
+
const logger_1 = require("../../../util/logger");
|
|
16
|
+
const INVALID_DATA = JSON.stringify({
|
|
17
|
+
appData: { Warning: 'Invalid response data. Please contact the Crowdin support manager.' },
|
|
18
|
+
});
|
|
19
|
+
function handle() {
|
|
20
|
+
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const id = Number.parseInt(`${req.params.id}`, 10);
|
|
22
|
+
if (!Number.isInteger(id)) {
|
|
23
|
+
return res.status(400).json({
|
|
24
|
+
error: {
|
|
25
|
+
message: 'Invalid error log id',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const userError = yield (0, storage_1.getStorage)().getUserError({
|
|
30
|
+
id,
|
|
31
|
+
crowdinId: req.crowdinContext.crowdinId,
|
|
32
|
+
integrationId: req.crowdinContext.clientId,
|
|
33
|
+
});
|
|
34
|
+
if (!userError) {
|
|
35
|
+
req.logInfo(`Error log ${id} not found`);
|
|
36
|
+
return res.status(404).json({
|
|
37
|
+
error: {
|
|
38
|
+
message: `Error log with ID ${id} not found`,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!userError.data || !(0, util_1.isJson)(userError.data)) {
|
|
43
|
+
(0, logger_1.logError)(userError);
|
|
44
|
+
return res.send({ data: INVALID_DATA });
|
|
45
|
+
}
|
|
46
|
+
res.send({ data: userError.data });
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { Response } from 'express';
|
|
2
|
+
export declare function parseUserErrorsQuery(query?: any): {
|
|
3
|
+
limit: number;
|
|
4
|
+
offset: number;
|
|
5
|
+
orderDirection: "asc" | "desc";
|
|
6
|
+
search: any;
|
|
7
|
+
};
|
|
2
8
|
export default function handle(): (req: import("express").Request | import("../../..").CrowdinClientRequest, res: Response, next: Function) => void;
|
|
@@ -9,27 +9,49 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.parseUserErrorsQuery = parseUserErrorsQuery;
|
|
12
13
|
exports.default = handle;
|
|
13
14
|
const storage_1 = require("../../../storage");
|
|
14
15
|
const util_1 = require("../../../util");
|
|
15
|
-
const
|
|
16
|
+
const DEFAULT_LIMIT = 10;
|
|
17
|
+
const MAX_LIMIT = 100;
|
|
18
|
+
const MAX_SEARCH_LENGTH = 255;
|
|
19
|
+
function parseInteger(value, fallback) {
|
|
20
|
+
const parsed = Number.parseInt(`${value}`, 10);
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
22
|
+
}
|
|
23
|
+
function parseUserErrorsQuery(query = {}) {
|
|
24
|
+
const search = typeof query.search === 'string' ? query.search.trim().slice(0, MAX_SEARCH_LENGTH) : '';
|
|
25
|
+
return {
|
|
26
|
+
limit: Math.min(Math.max(parseInteger(query.limit, DEFAULT_LIMIT), 1), MAX_LIMIT),
|
|
27
|
+
offset: Math.max(parseInteger(query.offset, 0), 0),
|
|
28
|
+
orderDirection: (query.order === 'asc' ? 'asc' : 'desc'),
|
|
29
|
+
search: search || undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
16
32
|
function handle() {
|
|
17
33
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const { limit, offset, orderDirection, search } = parseUserErrorsQuery(req.query);
|
|
35
|
+
const filter = {
|
|
36
|
+
crowdinId: req.crowdinContext.crowdinId,
|
|
37
|
+
integrationId: req.crowdinContext.clientId,
|
|
38
|
+
search,
|
|
39
|
+
};
|
|
40
|
+
req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId} (limit ${limit}, offset ${offset})`);
|
|
41
|
+
const [userTimezone, userErrors, total] = yield Promise.all([
|
|
42
|
+
req.crowdinApiClient.usersApi
|
|
43
|
+
.getAuthenticatedUser()
|
|
44
|
+
.then(({ data }) => data.timezone)
|
|
45
|
+
.catch((e) => {
|
|
46
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
47
|
+
req.logError(`Can't load user timezone, falling back to server timezone: ${errorMessage}`);
|
|
48
|
+
return undefined;
|
|
49
|
+
}),
|
|
50
|
+
(0, storage_1.getStorage)().getUserErrors(Object.assign(Object.assign({}, filter), { limit, offset, orderDirection })),
|
|
51
|
+
(0, storage_1.getStorage)().countUserErrors(filter),
|
|
52
|
+
]);
|
|
53
|
+
const data = userErrors.map((userError) => (Object.assign(Object.assign({}, userError), { formattedDate: (0, util_1.getFormattedDate)({ date: new Date(+userError.createdAt), userTimezone }) })));
|
|
54
|
+
req.logInfo(`Returning ${data.length} of ${total} user errors`);
|
|
55
|
+
res.send({ data, total });
|
|
34
56
|
}));
|
|
35
57
|
}
|
|
@@ -43,6 +43,7 @@ const settings_save_1 = __importDefault(require("./handlers/settings-save"));
|
|
|
43
43
|
const subscription_info_1 = __importDefault(require("./handlers/subscription-info"));
|
|
44
44
|
const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
|
|
45
45
|
const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
|
|
46
|
+
const user_error_detail_1 = __importDefault(require("./handlers/user-error-detail"));
|
|
46
47
|
const user_errors_1 = __importDefault(require("./handlers/user-errors"));
|
|
47
48
|
const users_1 = __importDefault(require("./handlers/users"));
|
|
48
49
|
const invite_users_1 = __importDefault(require("./handlers/invite-users"));
|
|
@@ -240,6 +241,13 @@ function register({ config, app }) {
|
|
|
240
241
|
moduleKey: integrationLogic.key,
|
|
241
242
|
isIntegration: true,
|
|
242
243
|
}), (0, integration_credentials_1.default)({ config, integration: integrationLogic }), (0, user_errors_1.default)());
|
|
244
|
+
app.get('/api/user-errors/:id', json_response_1.handle, (0, crowdin_client_1.handle)({
|
|
245
|
+
config,
|
|
246
|
+
optional: false,
|
|
247
|
+
checkSubscriptionExpiration: true,
|
|
248
|
+
moduleKey: integrationLogic.key,
|
|
249
|
+
isIntegration: true,
|
|
250
|
+
}), (0, integration_credentials_1.default)({ config, integration: integrationLogic }), (0, user_error_detail_1.default)());
|
|
243
251
|
app.get('/api/users', json_response_1.handle, (0, crowdin_client_1.handle)({
|
|
244
252
|
config,
|
|
245
253
|
optional: false,
|