@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.
@@ -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
- files = (0, files_1.skipFilesByRegex)(files, integration.skipIntegrationNodes);
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 = (_a = appSettings === null || appSettings === void 0 ? void 0 : appSettings.includeByFilePath) === null || _a === void 0 ? void 0 : _a.split('\n').filter(Boolean);
40
- const excludePatterns = (_b = appSettings === null || appSettings === void 0 ? void 0 : appSettings.excludeByFilePath) === null || _b === void 0 ? void 0 : _b.split('\n').filter(Boolean);
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 (((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.isNew) ||
44
- ((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.isUpdated) ||
45
- ((_h = (_g = integration.filtering) === null || _g === void 0 ? void 0 : _g.integrationFileStatus) === null || _h === void 0 ? void 0 : _h.notSynced)) {
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
- files = (0, files_1.skipFilesByRegex)(files, integration.skipIntegrationNodes);
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: boolean;
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 => catchRejection(e, 'Can\'t save settings'))
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
- closeModal(settingsModal);
1335
- {{#if reloadOnConfigSave}}
1336
- getIntegrationData(true);
1337
- getCrowdinData();
1338
- {{/if}}
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.init({
30
- dsn: "{{sentryData.dsn}}",
31
- environment: "frontend",
32
- replaysSessionSampleRate: 0,
33
- replaysOnErrorSampleRate: 1.0,
34
- integrations: [new Sentry.Replay()],
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
- Sentry.configureScope(function(scope) {
38
- scope.setTag("identifier", "{{sentryData.appIdentifier}}");
38
+ Sentry.configureScope(function(scope) {
39
+ scope.setTag("identifier", "{{sentryData.appIdentifier}}");
39
40
 
40
- AP.getContext(contextData => {
41
- const { user_id, ...rest } = contextData;
41
+ AP.getContext(contextData => {
42
+ const { user_id, ...rest } = contextData;
42
43
 
43
- user_id && scope.setUser({ id: user_id });
44
- scope.setTags(rest);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.92.0",
3
+ "version": "0.93.0",
4
4
  "description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",