@crowdin/app-project-module 2.9.0 → 2.10.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/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { getRequestCredentialsMasker, postRequestCredentialsMasker, maskKey } fr
5
5
  export { getRequestCredentialsMasker, postRequestCredentialsMasker, maskKey };
6
6
  export * from './types';
7
7
  export { Cron } from './util/cron';
8
+ /** for app code that fans work out inside one request or job: each branch opens its own log scope */
9
+ export { runWithContext } from '@crowdin/logs-formatter';
8
10
  export { express };
9
11
  export { reportInsight as reportAdvisorInsight } from './modules/advisor-inspector/report-insight';
10
12
  export declare const metadataStore: CrowdinMetadataStore;
package/out/index.js CHANGED
@@ -48,7 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  return (mod && mod.__esModule) ? mod : { "default": mod };
49
49
  };
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.metadataStore = exports.reportAdvisorInsight = exports.express = exports.maskKey = exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0;
51
+ exports.metadataStore = exports.reportAdvisorInsight = exports.express = exports.runWithContext = exports.maskKey = exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0;
52
52
  exports.createApp = createApp;
53
53
  exports.addCrowdinEndpoints = addCrowdinEndpoints;
54
54
  const logsFormatter = __importStar(require("@crowdin/logs-formatter"));
@@ -111,6 +111,9 @@ const automationAction = __importStar(require("./modules/automation-action"));
111
111
  const authGuard = __importStar(require("./modules/auth-guard"));
112
112
  const subscription_1 = require("./util/subscription");
113
113
  __exportStar(require("./types"), exports);
114
+ /** for app code that fans work out inside one request or job: each branch opens its own log scope */
115
+ var logs_formatter_1 = require("@crowdin/logs-formatter");
116
+ Object.defineProperty(exports, "runWithContext", { enumerable: true, get: function () { return logs_formatter_1.runWithContext; } });
114
117
  var report_insight_1 = require("./modules/advisor-inspector/report-insight");
115
118
  Object.defineProperty(exports, "reportAdvisorInsight", { enumerable: true, get: function () { return report_insight_1.reportInsight; } });
116
119
  exports.metadataStore = {
@@ -159,6 +162,12 @@ function addCrowdinEndpoints(app, clientConfig) {
159
162
  logger.initialize(config);
160
163
  storage.initialize(config);
161
164
  cron.initialize(config);
165
+ // Mounted first, so that even migration and body-parsing failures are attributed to their request
166
+ if (!config.disableLogsFormatter) {
167
+ logsFormatter.setup({ appIdentifier: config.identifier });
168
+ logsFormatter.registerSanitizer(log_sanitizer_1.crowdinLogSanitizer);
169
+ app.use(logsFormatter.contextResolverMiddleware());
170
+ }
162
171
  // Middleware to ensure delayed migration before handling requests
163
172
  app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
164
173
  try {
@@ -185,9 +194,7 @@ function addCrowdinEndpoints(app, clientConfig) {
185
194
  }
186
195
  });
187
196
  if (!config.disableLogsFormatter) {
188
- logsFormatter.setup();
189
- logsFormatter.registerSanitizer(log_sanitizer_1.crowdinLogSanitizer);
190
- app.use(logsFormatter.contextResolverMiddleware());
197
+ // stays after body parsing: it serves POST /_/log from req.body
191
198
  app.use(logsFormatter.expressMiddleware());
192
199
  }
193
200
  app.use('/assets', (0, static_files_1.serveStatic)(config, config.assetsPath || 'static'));
@@ -0,0 +1,11 @@
1
+ import { CrowdinContextInfo, CrowdinCredentials } from '../../../types';
2
+ import { IntegrationCredentials } from '../types';
3
+ /**
4
+ * Background work (crons, webhooks, job re-runs) has no JWT - this builds the equivalent context
5
+ * from stored credentials, so the same consumers (logging, client preparation) work either way.
6
+ */
7
+ export declare function crowdinContextInfoFromCredentials({ projectId, crowdinCredentials, integrationCredentials, }: {
8
+ projectId: number;
9
+ crowdinCredentials: CrowdinCredentials;
10
+ integrationCredentials?: IntegrationCredentials;
11
+ }): CrowdinContextInfo;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.crowdinContextInfoFromCredentials = crowdinContextInfoFromCredentials;
4
+ /**
5
+ * Background work (crons, webhooks, job re-runs) has no JWT - this builds the equivalent context
6
+ * from stored credentials, so the same consumers (logging, client preparation) work either way.
7
+ */
8
+ function crowdinContextInfoFromCredentials({ projectId, crowdinCredentials, integrationCredentials, }) {
9
+ return Object.assign({ jwtPayload: {
10
+ context: {
11
+ project_id: projectId,
12
+ organization_id: crowdinCredentials.organizationId,
13
+ organization_domain: crowdinCredentials.domain,
14
+ user_id: crowdinCredentials.userId,
15
+ },
16
+ }, crowdinId: crowdinCredentials.id }, ((integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.id) && { clientId: integrationCredentials.id }));
17
+ }
@@ -15,6 +15,7 @@ exports.filesCron = filesCron;
15
15
  exports.filterFilesFromIntegrationRequest = filterFilesFromIntegrationRequest;
16
16
  exports.createOrUpdateSyncSettings = createOrUpdateSyncSettings;
17
17
  exports.removeFinishedJobs = removeFinishedJobs;
18
+ const logs_formatter_1 = require("@crowdin/logs-formatter");
18
19
  const storage_1 = require("../../../storage");
19
20
  const util_1 = require("../../../util");
20
21
  const token_1 = require("../../../util/app-functions/token");
@@ -22,50 +23,72 @@ const connection_1 = require("../../../util/connection");
22
23
  const logger_1 = require("../../../util/logger");
23
24
  const subscription_1 = require("../../../util/subscription");
24
25
  const types_1 = require("../types");
26
+ const context_1 = require("./context");
25
27
  const defaults_1 = require("./defaults");
26
28
  const files_1 = require("./files");
27
29
  const job_1 = require("./job");
28
30
  const snapshot_1 = require("./snapshot");
29
31
  const types_2 = require("./types");
32
+ // Maximum number of organizations processed concurrently during a cron run.
33
+ // Keeps DB connection and API usage bounded regardless of how many orgs are registered.
34
+ const CRON_ORG_CONCURRENCY = (0, util_1.getEnvInt)('CRON_ORG_CONCURRENCY', 5);
35
+ function runJobForOrganization(_a) {
36
+ return __awaiter(this, arguments, void 0, function* ({ config, integration, job, crowdinCredentials, }) {
37
+ const { token, client: crowdinClient } = yield (0, connection_1.prepareCrowdinClient)({
38
+ config,
39
+ credentials: crowdinCredentials,
40
+ autoRenew: true,
41
+ });
42
+ const { expired } = yield (0, subscription_1.checkSubscription)({
43
+ config,
44
+ token,
45
+ organization: crowdinCredentials.id,
46
+ accountType: crowdinCredentials.type,
47
+ });
48
+ if (expired) {
49
+ (0, logger_1.log)(`Subscription expired. Skipping job [${job.expression}] for organization ${crowdinCredentials.id}`);
50
+ return;
51
+ }
52
+ const integrationCredentialsList = yield (0, storage_1.getStorage)().getAllIntegrationCredentials(crowdinCredentials.id);
53
+ const allIntegrationConfigs = yield (0, storage_1.getStorage)().getAllIntegrationConfigs(crowdinCredentials.id);
54
+ for (const integrationCredentials of integrationCredentialsList) {
55
+ const integrationConfig = allIntegrationConfigs.find(({ integrationId }) => integrationId === integrationCredentials.id);
56
+ const projectId = (0, token_1.getProjectId)(integrationCredentials.id);
57
+ (0, logger_1.setLogCrowdinContext)((0, context_1.crowdinContextInfoFromCredentials)({ projectId, crowdinCredentials, integrationCredentials }));
58
+ const credentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
59
+ const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
60
+ const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config) ? JSON.parse(integrationConfig.config) : undefined;
61
+ (0, logger_1.log)(`Executing task for cron job with expression [${job.expression}] for project ${projectId}`);
62
+ yield job.task({
63
+ projectId,
64
+ client: crowdinClient,
65
+ credentials,
66
+ rootFolder,
67
+ settings: intConfig,
68
+ });
69
+ (0, logger_1.log)(`Task for cron job with expression [${job.expression}] for project ${projectId} completed`);
70
+ }
71
+ });
72
+ }
30
73
  function runJob(_a) {
31
74
  return __awaiter(this, arguments, void 0, function* ({ config, integration, job, }) {
32
75
  (0, logger_1.log)(`Starting cron job with expression [${job.expression}]`);
33
76
  const crowdinCredentialsList = yield (0, storage_1.getStorage)().getAllCrowdinCredentials();
34
- yield Promise.all(crowdinCredentialsList.map((crowdinCredentials) => __awaiter(this, void 0, void 0, function* () {
35
- const { token, client: crowdinClient } = yield (0, connection_1.prepareCrowdinClient)({
36
- config,
37
- credentials: crowdinCredentials,
38
- autoRenew: true,
39
- });
40
- const { expired } = yield (0, subscription_1.checkSubscription)({
41
- config,
42
- token,
43
- organization: crowdinCredentials.id,
44
- accountType: crowdinCredentials.type,
77
+ for (let i = 0; i < crowdinCredentialsList.length; i += CRON_ORG_CONCURRENCY) {
78
+ const batch = crowdinCredentialsList.slice(i, i + CRON_ORG_CONCURRENCY);
79
+ const results = yield Promise.allSettled(
80
+ // per-organization scope, for the same reason processSyncSettings opens one per branch
81
+ batch.map((crowdinCredentials) => (0, logs_formatter_1.runWithContext)((0, logger_1.logContext)(crowdinCredentials), () => runJobForOrganization({ config, integration, job, crowdinCredentials }))));
82
+ results.forEach((result, index) => {
83
+ if (result.status === 'rejected') {
84
+ // the rejection landed here, outside the branch's scope, so reopen its tenant
85
+ (0, logs_formatter_1.runWithContext)((0, logger_1.logContext)(batch[index]), () => {
86
+ (0, logger_1.log)(`Cron job with expression [${job.expression}] failed for organization '${batch[index].id}'`);
87
+ (0, logger_1.logError)(result.reason);
88
+ });
89
+ }
45
90
  });
46
- if (expired) {
47
- (0, logger_1.log)(`Subscription expired. Skipping job [${job.expression}] for organization ${crowdinCredentials.id}`);
48
- return;
49
- }
50
- const integrationCredentialsList = yield (0, storage_1.getStorage)().getAllIntegrationCredentials(crowdinCredentials.id);
51
- const allIntegrationConfigs = yield (0, storage_1.getStorage)().getAllIntegrationConfigs(crowdinCredentials.id);
52
- for (const integrationCredentials of integrationCredentialsList) {
53
- const integrationConfig = allIntegrationConfigs.find(({ integrationId }) => integrationId === integrationCredentials.id);
54
- const projectId = (0, token_1.getProjectId)(integrationCredentials.id);
55
- const credentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
56
- const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
57
- const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config) ? JSON.parse(integrationConfig.config) : undefined;
58
- (0, logger_1.log)(`Executing task for cron job with expression [${job.expression}] for project ${projectId}`);
59
- yield job.task({
60
- projectId,
61
- client: crowdinClient,
62
- credentials,
63
- rootFolder,
64
- settings: intConfig,
65
- });
66
- (0, logger_1.log)(`Task for cron job with expression [${job.expression}] for project ${projectId} completed`);
67
- }
68
- })));
91
+ }
69
92
  (0, logger_1.log)(`Cron job with expression [${job.expression}] completed`);
70
93
  });
71
94
  }
@@ -135,22 +158,27 @@ function filesCron(_a) {
135
158
  const syncSettingsList = yield (0, storage_1.getStorage)().getSyncSettingsBySchedule('schedule', period);
136
159
  const crowdinSyncSettings = syncSettingsList.filter((syncSettings) => syncSettings.provider === types_1.Provider.CROWDIN);
137
160
  const integrationSyncSettings = syncSettingsList.filter((syncSettings) => syncSettings.provider === types_1.Provider.INTEGRATION);
138
- const crowdinResults = yield Promise.allSettled(crowdinSyncSettings.map((syncSettings) => processSyncSettings({ config, integration, period, syncSettings })));
139
- crowdinResults.forEach((result) => {
140
- if (result.status === 'rejected') {
141
- (0, logger_1.logError)(result.reason);
142
- }
143
- });
144
- const integrationResults = yield Promise.allSettled(integrationSyncSettings.map((syncSettings) => processSyncSettings({ config, integration, period, syncSettings })));
145
- integrationResults.forEach((result) => {
146
- if (result.status === 'rejected') {
147
- (0, logger_1.logError)(result.reason);
148
- }
149
- });
161
+ yield Promise.all(crowdinSyncSettings.map((syncSettings) => processSyncSettings({ config, integration, period, syncSettings })));
162
+ yield Promise.all(integrationSyncSettings.map((syncSettings) => processSyncSettings({ config, integration, period, syncSettings })));
150
163
  (0, logger_1.log)(`Files cron job with period [${period}] completed`);
151
164
  });
152
165
  }
153
- function processSyncSettings(_a) {
166
+ /**
167
+ * Each branch of the fan-out gets a scope of its own: the tick's scope is shared by every tenant
168
+ * due for sync, so a context written there stamps the other tenants' log lines.
169
+ */
170
+ function processSyncSettings(params) {
171
+ return (0, logs_formatter_1.runWithContext)({}, () => __awaiter(this, void 0, void 0, function* () {
172
+ try {
173
+ yield processSyncSettingsScoped(params);
174
+ }
175
+ catch (e) {
176
+ (0, logger_1.log)(`Files sync failed for organization '${params.syncSettings.crowdinId}'`);
177
+ (0, logger_1.logError)(e);
178
+ }
179
+ }));
180
+ }
181
+ function processSyncSettingsScoped(_a) {
154
182
  return __awaiter(this, arguments, void 0, function* ({ config, integration, period, syncSettings, }) {
155
183
  var _b, _c, _d, _e;
156
184
  let projectData;
@@ -171,18 +199,8 @@ function processSyncSettings(_a) {
171
199
  return;
172
200
  }
173
201
  const projectId = (0, token_1.getProjectId)(integrationCredentials.id);
174
- const context = {
175
- jwtPayload: {
176
- context: {
177
- project_id: projectId,
178
- organization_id: crowdinCredentials.organizationId,
179
- organization_domain: crowdinCredentials.domain,
180
- user_id: crowdinCredentials.userId,
181
- },
182
- },
183
- crowdinId: crowdinCredentials.id,
184
- clientId: integrationCredentials.id,
185
- };
202
+ const context = (0, context_1.crowdinContextInfoFromCredentials)({ projectId, crowdinCredentials, integrationCredentials });
203
+ (0, logger_1.setLogCrowdinContext)(context);
186
204
  const logInfo = (0, logger_1.withContext)(context);
187
205
  try {
188
206
  const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({
@@ -11,10 +11,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.runAsJob = runAsJob;
13
13
  exports.reRunInProgressJobs = reRunInProgressJobs;
14
+ const logs_formatter_1 = require("@crowdin/logs-formatter");
14
15
  const storage_1 = require("../../../storage");
15
16
  const token_1 = require("../../../util/app-functions/token");
16
17
  const connection_1 = require("../../../util/connection");
18
+ const util_1 = require("../../../util");
17
19
  const logger_1 = require("../../../util/logger");
20
+ const context_1 = require("./context");
18
21
  const cron_1 = require("./cron");
19
22
  const defaults_1 = require("./defaults");
20
23
  const types_1 = require("./types");
@@ -27,7 +30,12 @@ const blockingJobs = {
27
30
  [types_1.JobType.INTEGRATION_SETTINGS_SAVE]: [types_1.JobType.INTEGRATION_SETTINGS_SAVE],
28
31
  };
29
32
  const maxAttempts = 3;
30
- const store = {};
33
+ // Maximum number of in-progress jobs retried concurrently on startup.
34
+ const RERUN_JOB_CONCURRENCY = (0, util_1.getEnvInt)('RERUN_JOB_CONCURRENCY', 5);
35
+ // Maximum total etag entries held in the in-memory translation cache.
36
+ const TRANSLATION_CACHE_MAX = (0, util_1.getEnvInt)('TRANSLATION_CACHE_SIZE', 50000);
37
+ let store = {};
38
+ let storeSize = 0;
31
39
  function runAsJob(_a) {
32
40
  return __awaiter(this, arguments, void 0, function* ({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, responseData, jobCallback, onError, reRunJobId, forcePushTranslations, initiatedBy, }) {
33
41
  let jobId;
@@ -101,22 +109,22 @@ function runAsJob(_a) {
101
109
  yield storage.saveTranslationCache({ integrationId, crowdinId, fileId, languageId });
102
110
  }
103
111
  else {
112
+ if (storeSize >= TRANSLATION_CACHE_MAX) {
113
+ (0, logger_1.log)(`Translation etag cache reached limit of ${TRANSLATION_CACHE_MAX} entries, clearing`);
114
+ store = {};
115
+ storeSize = 0;
116
+ }
104
117
  if (!store[integrationId]) {
105
118
  store[integrationId] = {};
106
119
  }
107
120
  if (!store[integrationId][crowdinId]) {
108
- store[integrationId] = {
109
- [crowdinId]: {},
110
- };
121
+ store[integrationId][crowdinId] = {};
111
122
  }
112
123
  if (!store[integrationId][crowdinId][fileId]) {
113
- store[integrationId] = {
114
- [crowdinId]: {
115
- [fileId]: {},
116
- },
117
- };
124
+ store[integrationId][crowdinId][fileId] = {};
118
125
  }
119
126
  store[integrationId][crowdinId][fileId][languageId] = {};
127
+ storeSize++;
120
128
  }
121
129
  }
122
130
  let translation = null;
@@ -212,81 +220,92 @@ function runAsJob(_a) {
212
220
  }
213
221
  });
214
222
  }
223
+ function reRunJob(_a) {
224
+ return __awaiter(this, arguments, void 0, function* ({ config, activeJob }) {
225
+ const storage = (0, storage_1.getStorage)();
226
+ if (activeJob && ![types_1.JobStatus.IN_PROGRESS, types_1.JobStatus.CREATED].includes(activeJob.status)) {
227
+ return;
228
+ }
229
+ if (activeJob.attempt && activeJob.attempt >= maxAttempts) {
230
+ yield storage.updateJob({ id: activeJob.id, status: types_1.JobStatus.FAILED });
231
+ return;
232
+ }
233
+ const { integrationId, crowdinId, type, title, payload } = activeJob;
234
+ const integration = config.projectIntegration;
235
+ let crowdinClient;
236
+ const integrationCredentials = yield storage.getIntegrationCredentials(integrationId);
237
+ const crowdinCredentials = yield storage.getCrowdinCredentials(crowdinId);
238
+ const integrationConfig = yield storage.getIntegrationConfig(integrationId);
239
+ if (!integrationCredentials || !crowdinCredentials) {
240
+ return;
241
+ }
242
+ const projectId = (0, token_1.getProjectId)(integrationCredentials.id);
243
+ const context = (0, context_1.crowdinContextInfoFromCredentials)({
244
+ projectId,
245
+ crowdinCredentials,
246
+ integrationCredentials,
247
+ });
248
+ (0, logger_1.setLogCrowdinContext)(context);
249
+ try {
250
+ const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({
251
+ config,
252
+ credentials: crowdinCredentials,
253
+ autoRenew: true,
254
+ context,
255
+ });
256
+ crowdinClient = preparedCrowdinClient.client;
257
+ }
258
+ catch (e) {
259
+ (0, logger_1.logError)(e);
260
+ return;
261
+ }
262
+ const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
263
+ const credentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
264
+ const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config)
265
+ ? JSON.parse(integrationConfig.config)
266
+ : { schedule: '0', condition: '0' };
267
+ yield storage.updateJob({ attempt: +(activeJob.attempt || 0) + 1, id: activeJob.id });
268
+ if ([types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION].includes(type)) {
269
+ yield (0, cron_1.runUpdateProviderJob)({
270
+ integrationId,
271
+ crowdinId,
272
+ type,
273
+ title,
274
+ payload: JSON.parse(payload),
275
+ jobType: types_1.JobClientType.RERUN,
276
+ projectId,
277
+ client: crowdinClient,
278
+ integration,
279
+ context,
280
+ credentials,
281
+ rootFolder,
282
+ settings: intConfig,
283
+ reRunJobId: activeJob.id,
284
+ });
285
+ }
286
+ else if ([types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE, types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE].includes(type)) {
287
+ yield storage.updateJob({ status: types_1.JobStatus.CANCELED, id: activeJob.id });
288
+ }
289
+ });
290
+ }
215
291
  function reRunInProgressJobs(config) {
216
292
  return __awaiter(this, void 0, void 0, function* () {
217
- const storage = (0, storage_1.getStorage)();
218
- const inProgressJobs = yield storage.getAllInProgressJobs();
293
+ const inProgressJobs = yield (0, storage_1.getStorage)().getAllInProgressJobs();
219
294
  if (!(inProgressJobs === null || inProgressJobs === void 0 ? void 0 : inProgressJobs.length)) {
220
295
  return;
221
296
  }
222
- yield Promise.all(inProgressJobs.map((activeJob) => __awaiter(this, void 0, void 0, function* () {
223
- if (activeJob && ![types_1.JobStatus.IN_PROGRESS, types_1.JobStatus.CREATED].includes(activeJob.status)) {
224
- return;
225
- }
226
- if (activeJob.attempt && activeJob.attempt >= maxAttempts) {
227
- yield storage.updateJob({ id: activeJob.id, status: types_1.JobStatus.FAILED });
228
- return;
229
- }
230
- const { integrationId, crowdinId, type, title, payload } = activeJob;
231
- const integration = config.projectIntegration;
232
- let crowdinClient;
233
- const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(integrationId);
234
- const crowdinCredentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(crowdinId);
235
- const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(integrationId);
236
- if (!integrationCredentials || !crowdinCredentials) {
237
- return;
238
- }
239
- const projectId = (0, token_1.getProjectId)(integrationCredentials.id);
240
- const context = {
241
- jwtPayload: {
242
- context: {
243
- project_id: projectId,
244
- organization_id: crowdinCredentials.organizationId,
245
- organization_domain: crowdinCredentials.domain,
246
- user_id: crowdinCredentials.userId,
247
- },
248
- },
249
- };
250
- try {
251
- const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({
252
- config,
253
- credentials: crowdinCredentials,
254
- autoRenew: true,
255
- context,
256
- });
257
- crowdinClient = preparedCrowdinClient.client;
258
- }
259
- catch (e) {
260
- (0, logger_1.logError)(e);
261
- return;
262
- }
263
- const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
264
- const credentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
265
- const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config)
266
- ? JSON.parse(integrationConfig.config)
267
- : { schedule: '0', condition: '0' };
268
- yield storage.updateJob({ attempt: +(activeJob.attempt || 0) + 1, id: activeJob.id });
269
- if ([types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION].includes(type)) {
270
- yield (0, cron_1.runUpdateProviderJob)({
271
- integrationId,
272
- crowdinId,
273
- type,
274
- title,
275
- payload: JSON.parse(payload),
276
- jobType: types_1.JobClientType.RERUN,
277
- projectId,
278
- client: crowdinClient,
279
- integration,
280
- context,
281
- credentials,
282
- rootFolder,
283
- settings: intConfig,
284
- reRunJobId: activeJob.id,
285
- });
286
- }
287
- else if ([types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE, types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE].includes(type)) {
288
- yield storage.updateJob({ status: types_1.JobStatus.CANCELED, id: activeJob.id });
289
- }
290
- })));
297
+ for (let i = 0; i < inProgressJobs.length; i += RERUN_JOB_CONCURRENCY) {
298
+ const batch = inProgressJobs.slice(i, i + RERUN_JOB_CONCURRENCY);
299
+ yield Promise.all(
300
+ // re-runs start at app.listen, outside any request, so each job gets a scope of its own
301
+ batch.map((activeJob) => (0, logs_formatter_1.runWithContext)({}, () => __awaiter(this, void 0, void 0, function* () {
302
+ try {
303
+ yield reRunJob({ config, activeJob });
304
+ }
305
+ catch (e) {
306
+ (0, logger_1.logError)(e);
307
+ }
308
+ }))));
309
+ }
291
310
  });
292
311
  }
@@ -1,4 +1,5 @@
1
1
  import Crowdin from '@crowdin/crowdin-api-client';
2
+ import { Channel, ConsumeMessage } from 'amqplib';
2
3
  import { Config, CrowdinContextInfo } from '../../../types';
3
4
  import { AppSettings, IntegrationLogic, IntegrationSyncSettings, Provider, UpdateCrowdinWebhookPayloadsArgs } from '../types';
4
5
  export declare const HookEvents: any;
@@ -35,6 +36,7 @@ export declare function prepareWebhookData({ config, integration, provider, webh
35
36
  settings: AppSettings;
36
37
  syncSettings: null;
37
38
  preparedIntegrationCredentials: null;
39
+ logContext?: undefined;
38
40
  } | {
39
41
  projectId: number;
40
42
  crowdinClient: {
@@ -45,6 +47,7 @@ export declare function prepareWebhookData({ config, integration, provider, webh
45
47
  rootFolder: import("@crowdin/crowdin-api-client").SourceFilesModel.Directory | undefined;
46
48
  settings: AppSettings;
47
49
  syncSettings: IntegrationSyncSettings | null;
50
+ logContext: import("@crowdin/logs-formatter").Context;
48
51
  }>;
49
52
  export declare function updateCrowdinFromWebhookRequest(args: UpdateCrowdinWebhookPayloadsArgs): Promise<void>;
50
53
  export declare function listenQueueMessage({ config, integration, queueName, queueUrl, }: {
@@ -53,3 +56,11 @@ export declare function listenQueueMessage({ config, integration, queueName, que
53
56
  queueUrl: string;
54
57
  queueName: string;
55
58
  }): Promise<void>;
59
+ export declare function processMessages({ channel, messagesToAck, webhooksData, webhooksInfo, }: {
60
+ webhooksData: Promise<void>[];
61
+ webhooksInfo: {
62
+ [key: string]: any;
63
+ };
64
+ channel: Channel;
65
+ messagesToAck: ConsumeMessage[];
66
+ }): Promise<void>;