@crowdin/app-project-module 0.92.0 → 0.93.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/modules/integration/handlers/integration-data.js +10 -7
- package/out/modules/integration/handlers/settings-save.js +15 -0
- package/out/modules/integration/types.d.ts +21 -0
- package/out/modules/integration/util/defaults.js +11 -0
- package/out/modules/integration/util/snapshot.js +5 -1
- package/out/types.d.ts +1 -1
- package/out/views/main.handlebars +36 -7
- package/out/views/partials/head.handlebars +18 -14
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ const types_1 = require("../types");
|
|
|
16
16
|
const storage_1 = require("../../../storage");
|
|
17
17
|
function handle(integration) {
|
|
18
18
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
19
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
20
20
|
const { parent_id: parentId, search, page } = req.query;
|
|
21
21
|
req.logInfo('Received request to get integration data');
|
|
22
22
|
let message;
|
|
@@ -34,15 +34,18 @@ function handle(integration) {
|
|
|
34
34
|
else {
|
|
35
35
|
files = result || [];
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
if ((appSettings === null || appSettings === void 0 ? void 0 : appSettings.skipIntegrationNodesToggle) === true ||
|
|
38
|
+
((appSettings === null || appSettings === void 0 ? void 0 : appSettings.skipIntegrationNodesToggle) === undefined && ((_a = integration.skipIntegrationNodesToggle) === null || _a === void 0 ? void 0 : _a.value))) {
|
|
39
|
+
files = (0, files_1.skipFilesByRegex)(files, integration.skipIntegrationNodes);
|
|
40
|
+
}
|
|
38
41
|
if (integration.filterByPathIntegrationFiles) {
|
|
39
|
-
const includePatterns = (
|
|
40
|
-
const excludePatterns = (
|
|
42
|
+
const includePatterns = (_b = appSettings === null || appSettings === void 0 ? void 0 : appSettings.includeByFilePath) === null || _b === void 0 ? void 0 : _b.split('\n').filter(Boolean);
|
|
43
|
+
const excludePatterns = (_c = appSettings === null || appSettings === void 0 ? void 0 : appSettings.excludeByFilePath) === null || _c === void 0 ? void 0 : _c.split('\n').filter(Boolean);
|
|
41
44
|
files = (0, files_1.filterFilesByPath)(files, includePatterns, excludePatterns);
|
|
42
45
|
}
|
|
43
|
-
if (((
|
|
44
|
-
((
|
|
45
|
-
((
|
|
46
|
+
if (((_e = (_d = integration.filtering) === null || _d === void 0 ? void 0 : _d.integrationFileStatus) === null || _e === void 0 ? void 0 : _e.isNew) ||
|
|
47
|
+
((_g = (_f = integration.filtering) === null || _f === void 0 ? void 0 : _f.integrationFileStatus) === null || _g === void 0 ? void 0 : _g.isUpdated) ||
|
|
48
|
+
((_j = (_h = integration.filtering) === null || _h === void 0 ? void 0 : _h.integrationFileStatus) === null || _j === void 0 ? void 0 : _j.notSynced)) {
|
|
46
49
|
const syncedData = yield (0, storage_1.getStorage)().getSyncedData(clientId, crowdinId, types_1.Provider.INTEGRATION);
|
|
47
50
|
const syncedFiles = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.files) ? JSON.parse(syncedData.files) : [];
|
|
48
51
|
const lastSyncTimestamp = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.updatedAt) ? Number(syncedData.updatedAt) : null;
|
|
@@ -25,6 +25,21 @@ function handle(config, integration) {
|
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
if (integration.validateSettings) {
|
|
29
|
+
const validationResult = yield integration.validateSettings({
|
|
30
|
+
client: req.crowdinApiClient,
|
|
31
|
+
credentials: req.integrationCredentials,
|
|
32
|
+
appSettings,
|
|
33
|
+
});
|
|
34
|
+
if (validationResult && Object.keys(validationResult).length > 0) {
|
|
35
|
+
return res.status(400).json({
|
|
36
|
+
error: {
|
|
37
|
+
type: 'validation_error',
|
|
38
|
+
details: validationResult,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
28
43
|
const clientId = req.crowdinContext.clientId;
|
|
29
44
|
req.logInfo(`Saving settings ${JSON.stringify(appSettings, null, 2)}`);
|
|
30
45
|
const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(clientId);
|
|
@@ -19,6 +19,16 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
19
19
|
* flag that defines if the app should have a dedicated root folder in Crowdin files, default 'false'
|
|
20
20
|
*/
|
|
21
21
|
withRootFolder?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Validate integration settings before saving
|
|
24
|
+
*/
|
|
25
|
+
validateSettings?: ({ client, credentials, appSettings, }: {
|
|
26
|
+
client: Crowdin;
|
|
27
|
+
credentials: any;
|
|
28
|
+
appSettings: AppSettings;
|
|
29
|
+
}) => Promise<{
|
|
30
|
+
[key: string]: string;
|
|
31
|
+
} | null>;
|
|
22
32
|
/**
|
|
23
33
|
* function which will be used to check connection with integration service
|
|
24
34
|
*/
|
|
@@ -186,7 +196,18 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
186
196
|
icon: boolean;
|
|
187
197
|
close: boolean;
|
|
188
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Skip integration nodes
|
|
201
|
+
*/
|
|
189
202
|
skipIntegrationNodes?: SkipIntegrationNodes;
|
|
203
|
+
/**
|
|
204
|
+
* Configuration for toggling skipIntegrationNodes functionality with custom title and description
|
|
205
|
+
*/
|
|
206
|
+
skipIntegrationNodesToggle?: {
|
|
207
|
+
title: string;
|
|
208
|
+
description: string;
|
|
209
|
+
value: boolean;
|
|
210
|
+
};
|
|
190
211
|
/**
|
|
191
212
|
* Async progress checking time interval to update job progress, im ms.
|
|
192
213
|
*
|
|
@@ -287,6 +287,17 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
287
287
|
helpText: 'Enter the path patterns for files or folders to exclude. Use wildcard selectors like `*` and `**` to match multiple files. Example: `/drafts/**`',
|
|
288
288
|
});
|
|
289
289
|
}
|
|
290
|
+
if (integration.skipIntegrationNodes && integration.skipIntegrationNodesToggle) {
|
|
291
|
+
defaultSettings.push({
|
|
292
|
+
key: 'skipIntegrationNodesToggle',
|
|
293
|
+
label: integration.skipIntegrationNodesToggle.title,
|
|
294
|
+
type: 'checkbox',
|
|
295
|
+
helpText: integration.skipIntegrationNodesToggle.description,
|
|
296
|
+
defaultValue: integration.skipIntegrationNodesToggle.value,
|
|
297
|
+
category: types_1.DefaultCategory.GENERAL,
|
|
298
|
+
position: 7,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
290
301
|
return [...defaultSettings, ...fields];
|
|
291
302
|
});
|
|
292
303
|
if (!integration.checkConnection) {
|
|
@@ -51,6 +51,7 @@ function getOneLevelFetchingFiles(integration, integrationCredentials, integrati
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
function getIntegrationSnapshot(integration, integrationCredentials, integrationSettings) {
|
|
54
|
+
var _a;
|
|
54
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
56
|
let files = [];
|
|
56
57
|
let integrationData = [];
|
|
@@ -59,7 +60,10 @@ function getIntegrationSnapshot(integration, integrationCredentials, integration
|
|
|
59
60
|
if (integration.integrationOneLevelFetching) {
|
|
60
61
|
files = yield getOneLevelFetchingFiles(integration, integrationCredentials, integrationSettings, files);
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
+
if ((integrationSettings === null || integrationSettings === void 0 ? void 0 : integrationSettings.skipIntegrationNodesToggle) === true ||
|
|
64
|
+
((integrationSettings === null || integrationSettings === void 0 ? void 0 : integrationSettings.skipIntegrationNodesToggle) === null && ((_a = integration.skipIntegrationNodesToggle) === null || _a === void 0 ? void 0 : _a.value))) {
|
|
65
|
+
files = (0, files_1.skipFilesByRegex)(files, integration.skipIntegrationNodes);
|
|
66
|
+
}
|
|
63
67
|
// trick for compatibility in requests and set files
|
|
64
68
|
files = files.map((file) => (Object.assign(Object.assign({}, file), { parentId: file.parent_id || file.parentId,
|
|
65
69
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
package/out/types.d.ts
CHANGED
|
@@ -261,7 +261,7 @@ export interface ClientConfig extends ImagePath {
|
|
|
261
261
|
/**
|
|
262
262
|
* property that tells backend that AiProvider and AiPromptProvider modules can cooperate only with each one
|
|
263
263
|
*/
|
|
264
|
-
restrictAiToSameApp
|
|
264
|
+
restrictAiToSameApp?: boolean;
|
|
265
265
|
}
|
|
266
266
|
export interface Environments {
|
|
267
267
|
environments?: Environment | Environment[];
|
|
@@ -287,6 +287,7 @@
|
|
|
287
287
|
{{#if dependencySettings}}
|
|
288
288
|
data-dependency="{{dependencySettings}}"
|
|
289
289
|
{{/if}}
|
|
290
|
+
onchange="settingsFieldChange(this)"
|
|
290
291
|
>
|
|
291
292
|
</crowdin-checkbox>
|
|
292
293
|
{{/ifeq}}
|
|
@@ -312,6 +313,7 @@
|
|
|
312
313
|
data-dependency="{{dependencySettings}}"
|
|
313
314
|
{{/if}}
|
|
314
315
|
is-position-fixed
|
|
316
|
+
onchange="settingsFieldChange(this)"
|
|
315
317
|
>
|
|
316
318
|
{{#each options}}
|
|
317
319
|
<option
|
|
@@ -340,6 +342,7 @@
|
|
|
340
342
|
data-dependency="{{dependencySettings}}"
|
|
341
343
|
{{/if}}
|
|
342
344
|
value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
|
|
345
|
+
onchange="settingsFieldChange(this)"
|
|
343
346
|
>
|
|
344
347
|
</crowdin-input>
|
|
345
348
|
{{/ifeq}}
|
|
@@ -357,7 +360,9 @@
|
|
|
357
360
|
{{#if dependencySettings}}
|
|
358
361
|
data-dependency="{{dependencySettings}}"
|
|
359
362
|
{{/if}}
|
|
360
|
-
value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
|
|
363
|
+
value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
|
|
364
|
+
onchange="settingsFieldChange(this)"
|
|
365
|
+
>
|
|
361
366
|
</crowdin-textarea>
|
|
362
367
|
{{/ifeq}}
|
|
363
368
|
{{#ifeq type "notice"}}
|
|
@@ -1315,6 +1320,7 @@
|
|
|
1315
1320
|
});
|
|
1316
1321
|
});
|
|
1317
1322
|
|
|
1323
|
+
let isValidationError = false;
|
|
1318
1324
|
settingsSaveBtn.setAttribute('disabled', true);
|
|
1319
1325
|
checkOrigin()
|
|
1320
1326
|
.then(restParams => fetch('api/settings' + restParams, {
|
|
@@ -1327,15 +1333,33 @@
|
|
|
1327
1333
|
showToast('Settings successfully saved');
|
|
1328
1334
|
config = configReq;
|
|
1329
1335
|
})
|
|
1330
|
-
.catch(e =>
|
|
1336
|
+
.catch(e => {
|
|
1337
|
+
if (e?.error && e?.error?.type === 'validation_error') {
|
|
1338
|
+
Object.keys(e?.error?.details).forEach(key => {
|
|
1339
|
+
const el = document.getElementById(`${key}-settings`);
|
|
1340
|
+
if (el) {
|
|
1341
|
+
el.setAttribute('error', 'true');
|
|
1342
|
+
el.setAttribute('error-text', e?.error?.details[key]);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
isValidationError = true;
|
|
1347
|
+
showToast(e?.error?.details?.message || 'Can\'t save settings');
|
|
1348
|
+
settingsSaveBtn.removeAttribute('disabled');
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
catchRejection(e, 'Can\'t save settings')
|
|
1352
|
+
})
|
|
1331
1353
|
.finally(() => {
|
|
1332
1354
|
unsetLoader('#settings-modal');
|
|
1333
1355
|
settingsSaveBtn.removeAttribute('disabled');
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1356
|
+
if (!isValidationError) {
|
|
1357
|
+
closeModal(settingsModal);
|
|
1358
|
+
{{#if reloadOnConfigSave}}
|
|
1359
|
+
getIntegrationData(true);
|
|
1360
|
+
getCrowdinData();
|
|
1361
|
+
{{/if}}
|
|
1362
|
+
}
|
|
1339
1363
|
});
|
|
1340
1364
|
}
|
|
1341
1365
|
{{else}}
|
|
@@ -1770,6 +1794,11 @@
|
|
|
1770
1794
|
modal.style.display = 'none';
|
|
1771
1795
|
modal.close()
|
|
1772
1796
|
}
|
|
1797
|
+
|
|
1798
|
+
function settingsFieldChange(el) {
|
|
1799
|
+
el.removeAttribute('error');
|
|
1800
|
+
el.removeAttribute('error-text');
|
|
1801
|
+
}
|
|
1773
1802
|
</script>
|
|
1774
1803
|
|
|
1775
1804
|
</html>
|
|
@@ -26,24 +26,28 @@
|
|
|
26
26
|
crossorigin="anonymous"
|
|
27
27
|
></script>
|
|
28
28
|
<script>
|
|
29
|
-
Sentry
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
if (typeof Sentry !== 'undefined') {
|
|
30
|
+
Sentry.init({
|
|
31
|
+
dsn: "{{sentryData.dsn}}",
|
|
32
|
+
environment: "frontend",
|
|
33
|
+
replaysSessionSampleRate: 0,
|
|
34
|
+
replaysOnErrorSampleRate: 1.0,
|
|
35
|
+
integrations: [new Sentry.Replay()],
|
|
36
|
+
});
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
Sentry.configureScope(function(scope) {
|
|
39
|
+
scope.setTag("identifier", "{{sentryData.appIdentifier}}");
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
AP.getContext(contextData => {
|
|
42
|
+
const { user_id, ...rest } = contextData;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
user_id && scope.setUser({ id: user_id });
|
|
45
|
+
scope.setTags(rest);
|
|
46
|
+
});
|
|
45
47
|
});
|
|
46
|
-
}
|
|
48
|
+
} else {
|
|
49
|
+
console.warn('Sentry is not available. This might be due to ad/tracking blockers or network issues.');
|
|
50
|
+
}
|
|
47
51
|
</script>
|
|
48
52
|
{{/if}}
|
|
49
53
|
</head>
|
package/package.json
CHANGED