@crowdin/app-project-module 0.74.0 → 0.76.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.
@@ -108,6 +108,8 @@ const updateCrowdinTest = ({ appConfig, integrationTestConfig, }) => __awaiter(v
108
108
  type: types_1.JobClientType.MANUAL,
109
109
  fetchTranslation: jest.fn(),
110
110
  translationUploaded: jest.fn(),
111
+ markFilesAsUnsynced: jest.fn(),
112
+ unmarkFilesAsUnsynced: jest.fn(),
111
113
  },
112
114
  });
113
115
  }, 'Fail to run method updateCrowdin()');
@@ -65,6 +65,8 @@ const updateIntegrationTest = ({ appConfig, integrationTestConfig, }) => __await
65
65
  type: types_1.JobClientType.MANUAL,
66
66
  fetchTranslation: jest.fn(),
67
67
  translationUploaded: jest.fn(),
68
+ markFilesAsUnsynced: jest.fn(),
69
+ unmarkFilesAsUnsynced: jest.fn(),
68
70
  },
69
71
  });
70
72
  }, 'Fail to run method updateIntegration()');
@@ -14,6 +14,8 @@ const crowdin_file_progress_1 = __importDefault(require("../integration/handlers
14
14
  const crowdin_files_1 = __importDefault(require("../integration/handlers/crowdin-files"));
15
15
  const crowdin_update_1 = __importDefault(require("../integration/handlers/crowdin-update"));
16
16
  const integration_data_1 = __importDefault(require("../integration/handlers/integration-data"));
17
+ const job_info_1 = __importDefault(require("../integration/handlers/job-info"));
18
+ const job_cancel_1 = __importDefault(require("../integration/handlers/job-cancel"));
17
19
  const integration_login_1 = __importDefault(require("../integration/handlers/integration-login"));
18
20
  const integration_update_1 = __importDefault(require("../integration/handlers/integration-update"));
19
21
  const settings_1 = __importDefault(require("../integration/handlers/settings"));
@@ -55,7 +57,7 @@ function getDefaultApiEndpointsManifest(config) {
55
57
  description: 'Get a list of synced files',
56
58
  documentationUrl: '/api-docs#tag/Files/operation/crowdin.files',
57
59
  }, {
58
- key: 'crowdin-files-api',
60
+ key: 'file-translation-progress-api',
59
61
  name: 'File Translation Progress',
60
62
  url: '/file-progress',
61
63
  method: types_1.RequestMethods.GET,
@@ -82,6 +84,20 @@ function getDefaultApiEndpointsManifest(config) {
82
84
  method: types_1.RequestMethods.POST,
83
85
  description: 'Update integration data',
84
86
  documentationUrl: '/api-docs#tag/Files/operation/integration.update',
87
+ }, {
88
+ key: 'job-get-api',
89
+ name: 'Job Status',
90
+ url: '/jobs',
91
+ method: types_1.RequestMethods.GET,
92
+ description: 'Get Job Info',
93
+ documentationUrl: '/api-docs#tag/Jobs/operation/job.get',
94
+ }, {
95
+ key: 'job-cancel-api',
96
+ name: 'Job Status',
97
+ url: '/jobs',
98
+ method: types_1.RequestMethods.DELETE,
99
+ description: 'Cancel Job',
100
+ documentationUrl: '/api-docs#tag/Jobs/operation/job.cancel',
85
101
  }, {
86
102
  key: 'settings-api',
87
103
  name: 'Get App Settings',
@@ -191,7 +207,7 @@ function addDefaultApiEndpoints(app, config) {
191
207
  * name: fileId
192
208
  * in: query
193
209
  * required: true
194
- * description: 'Filter branch by name. Get via [List Crowdin Files](#operation/crowdin.files)'
210
+ * description: 'Get via [List Crowdin Files](#operation/crowdin.files)'
195
211
  * schema:
196
212
  * type: integer
197
213
  * example: 102
@@ -294,6 +310,62 @@ function addDefaultApiEndpoints(app, config) {
294
310
  checkSubscriptionExpiration: true,
295
311
  moduleKey: config.projectIntegration.key,
296
312
  }), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, integration_update_1.default)(config, config.projectIntegration));
313
+ /**
314
+ * @openapi
315
+ * /jobs:
316
+ * get:
317
+ * tags:
318
+ * - 'Jobs'
319
+ * summary: 'Get Job Info'
320
+ * operationId: job.get
321
+ * parameters:
322
+ * - $ref: '#/components/parameters/ProjectId'
323
+ * - name: jobId
324
+ * in: query
325
+ * required: false
326
+ * schema:
327
+ * type: string
328
+ * example: 067da473-fc0b-43e3-b0a2-09d26af130c1
329
+ * responses:
330
+ * 200:
331
+ * description: 'Job information retrieved successfully'
332
+ * content:
333
+ * application/json:
334
+ * schema:
335
+ * $ref: '#/components/schemas/JobResponse'
336
+ */
337
+ app.get('/jobs', json_response_1.default, (0, crowdin_client_1.default)({
338
+ config,
339
+ optional: false,
340
+ checkSubscriptionExpiration: true,
341
+ moduleKey: config.projectIntegration.key,
342
+ }), (0, job_info_1.default)(config));
343
+ /**
344
+ * @openapi
345
+ * /jobs:
346
+ * delete:
347
+ * tags:
348
+ * - 'Jobs'
349
+ * summary: 'Cancel Job'
350
+ * operationId: job.cancel
351
+ * parameters:
352
+ * - $ref: '#/components/parameters/ProjectId'
353
+ * - name: jobId
354
+ * in: query
355
+ * required: false
356
+ * schema:
357
+ * type: string
358
+ * example: 067da473-fc0b-43e3-b0a2-09d26af130c1
359
+ * responses:
360
+ * 204:
361
+ * description: 'Job canceled successfully'
362
+ */
363
+ app.delete('/jobs', json_response_1.default, (0, crowdin_client_1.default)({
364
+ config,
365
+ optional: false,
366
+ checkSubscriptionExpiration: true,
367
+ moduleKey: config.projectIntegration.key,
368
+ }), (0, job_cancel_1.default)(config));
297
369
  /**
298
370
  * @openapi
299
371
  * /settings:
@@ -413,7 +485,7 @@ function addDefaultApiEndpoints(app, config) {
413
485
  * operationId: integration.fields
414
486
  * responses:
415
487
  * 200:
416
- * description: 'File translation progress'
488
+ * description: 'Login Form Fields'
417
489
  * content:
418
490
  * application/json:
419
491
  * schema:
@@ -70,25 +70,32 @@
70
70
  * - files
71
71
  * properties:
72
72
  * projectId:
73
- * description: 'Project Identifier. Get via [List Projects](https://developer.crowdin.com/api/v2/#operation/api.projects.getMany)'
73
+ * description: 'Project Id. Get via [List Projects](https://developer.crowdin.com/api/v2/#operation/api.projects.getMany)'
74
74
  * type: integer
75
75
  * example: 12
76
76
  * files:
77
+ * example: { 102: ['de', 'fr'], 999: ['uk'] }
77
78
  * type: object
78
- * example:
79
- * 102: ['uk', 'de']
80
- * additionalProperties:
81
- * type: array
82
- * items:
83
- * type: string
79
+ * description: |
80
+ * - **{fileId}** _(integer)_: Crowdin File Id. Get via [List Crowdin Files](#operation/crowdin.files)
81
+ * - **[{languageCode}]** _(array of strings)_: List Of Language Id. Get via [List Supported Languages](https://support.crowdin.com/developer/api/v2/#tag/Languages/operation/api.languages.getMany)
82
+ *
83
+ * **Example:**
84
+ * ```json
85
+ * {
86
+ * 102: ["de", "fr"],
87
+ * 999: ["uk"]
88
+ * }
89
+ * ```
84
90
  * UpdateResponse:
85
91
  * type: object
86
- * items:
87
- * anyOf:
88
- * - properties:
89
- * message:
90
- * type: string
91
- * example: 'File 102 Not Found'
92
+ * properties:
93
+ * jobId:
94
+ * type: string
95
+ * example: '067da473-fc0b-43e3-b0a2-09d26af130c1'
96
+ * message:
97
+ * type: string
98
+ * example: 'Another sync is running'
92
99
  * SettingsData:
93
100
  * type: object
94
101
  * example: {schedule: 0, condition: 0}
@@ -195,8 +202,17 @@
195
202
  * type: integer
196
203
  * example: 86
197
204
  * LoginFieldsResponse:
198
- * example: [{ name: 'email', description: 'User email' }, { name: 'password', description: 'User password' }]
199
- * type: object
205
+ * type: array
206
+ * items:
207
+ * type: object
208
+ * properties:
209
+ * key:
210
+ * type: string
211
+ * example: 'apiKey'
212
+ * name:
213
+ * type: string
214
+ * example: 'Service API key'
215
+ * example: [{ key: 'email', name: 'User email' }, { key: 'password', name: 'User password' }]
200
216
  * Login:
201
217
  * title: 'Login'
202
218
  * required:
@@ -211,9 +227,34 @@
211
227
  * $ref: '#/components/schemas/LoginData'
212
228
  * LoginData:
213
229
  * type: object
230
+ * description: 'Login Form Fields. Get via [Integration Login Form Fields](#operation/integration.fields)'
214
231
  * example: { email: 'user@crowdin.com', password: 'password' }
215
- * additionalProperties:
216
- * type: string
232
+ * Job:
233
+ * type: object
234
+ * properties:
235
+ * id:
236
+ * type: string
237
+ * description: 'The Unique Identifier For The Job.'
238
+ * example: '067da473-fc0b-43e3-b0a2-09d26af130c1'
239
+ * progress:
240
+ * type: integer
241
+ * description: 'The Progress Of The Job.'
242
+ * example: 94
243
+ * status:
244
+ * type: string
245
+ * description: 'The Current Status Of The Job.'
246
+ * example: 'inProgress'
247
+ * title:
248
+ * type: string
249
+ * description: 'The Title Of The Job.'
250
+ * example: 'Sync files to Crowdin'
251
+ * JobResponse:
252
+ * type: object
253
+ * properties:
254
+ * data:
255
+ * type: array
256
+ * items:
257
+ * $ref: '#/components/schemas/Job'
217
258
  *
218
259
  * parameters:
219
260
  * ProjectId:
@@ -71,25 +71,32 @@
71
71
  * - files
72
72
  * properties:
73
73
  * projectId:
74
- * description: 'Project Identifier. Get via [List Projects](https://developer.crowdin.com/api/v2/#operation/api.projects.getMany)'
74
+ * description: 'Project Id. Get via [List Projects](https://developer.crowdin.com/api/v2/#operation/api.projects.getMany)'
75
75
  * type: integer
76
76
  * example: 12
77
77
  * files:
78
+ * example: { 102: ['de', 'fr'], 999: ['uk'] }
78
79
  * type: object
79
- * example:
80
- * 102: ['uk', 'de']
81
- * additionalProperties:
82
- * type: array
83
- * items:
84
- * type: string
80
+ * description: |
81
+ * - **{fileId}** _(integer)_: Crowdin File Id. Get via [List Crowdin Files](#operation/crowdin.files)
82
+ * - **[{languageCode}]** _(array of strings)_: List Of Language Id. Get via [List Supported Languages](https://support.crowdin.com/developer/api/v2/#tag/Languages/operation/api.languages.getMany)
83
+ *
84
+ * **Example:**
85
+ * ```json
86
+ * {
87
+ * 102: ["de", "fr"],
88
+ * 999: ["uk"]
89
+ * }
90
+ * ```
85
91
  * UpdateResponse:
86
92
  * type: object
87
- * items:
88
- * anyOf:
89
- * - properties:
90
- * message:
91
- * type: string
92
- * example: 'File 102 Not Found'
93
+ * properties:
94
+ * jobId:
95
+ * type: string
96
+ * example: '067da473-fc0b-43e3-b0a2-09d26af130c1'
97
+ * message:
98
+ * type: string
99
+ * example: 'Another sync is running'
93
100
  * SettingsData:
94
101
  * type: object
95
102
  * example: {schedule: 0, condition: 0}
@@ -196,8 +203,17 @@
196
203
  * type: integer
197
204
  * example: 86
198
205
  * LoginFieldsResponse:
199
- * example: [{ name: 'email', description: 'User email' }, { name: 'password', description: 'User password' }]
200
- * type: object
206
+ * type: array
207
+ * items:
208
+ * type: object
209
+ * properties:
210
+ * key:
211
+ * type: string
212
+ * example: 'apiKey'
213
+ * name:
214
+ * type: string
215
+ * example: 'Service API key'
216
+ * example: [{ key: 'email', name: 'User email' }, { key: 'password', name: 'User password' }]
201
217
  * Login:
202
218
  * title: 'Login'
203
219
  * required:
@@ -212,9 +228,34 @@
212
228
  * $ref: '#/components/schemas/LoginData'
213
229
  * LoginData:
214
230
  * type: object
231
+ * description: 'Login Form Fields. Get via [Integration Login Form Fields](#operation/integration.fields)'
215
232
  * example: { email: 'user@crowdin.com', password: 'password' }
216
- * additionalProperties:
217
- * type: string
233
+ * Job:
234
+ * type: object
235
+ * properties:
236
+ * id:
237
+ * type: string
238
+ * description: 'The Unique Identifier For The Job.'
239
+ * example: '067da473-fc0b-43e3-b0a2-09d26af130c1'
240
+ * progress:
241
+ * type: integer
242
+ * description: 'The Progress Of The Job.'
243
+ * example: 94
244
+ * status:
245
+ * type: string
246
+ * description: 'The Current Status Of The Job.'
247
+ * example: 'inProgress'
248
+ * title:
249
+ * type: string
250
+ * description: 'The Title Of The Job.'
251
+ * example: 'Sync files to Crowdin'
252
+ * JobResponse:
253
+ * type: object
254
+ * properties:
255
+ * data:
256
+ * type: array
257
+ * items:
258
+ * $ref: '#/components/schemas/Job'
218
259
  *
219
260
  * parameters:
220
261
  * ProjectId:
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../../../util");
13
13
  const defaults_1 = require("../util/defaults");
14
14
  const logger_1 = require("../../../util/logger");
15
+ const files_1 = require("../util/files");
15
16
  function handle(config, integration) {
16
17
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
17
18
  req.logInfo('Loading crowdin files');
@@ -19,9 +20,18 @@ function handle(config, integration) {
19
20
  const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, req.crowdinApiClient, req.crowdinContext.jwtPayload.context.project_id);
20
21
  req.logInfo(`Loading files ${rootFolder ? `from folder ${rootFolder.id}` : 'from root'}`);
21
22
  try {
22
- const files = integration.getCrowdinFiles
23
+ let files = integration.getCrowdinFiles
23
24
  ? yield integration.getCrowdinFiles(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, rootFolder, req.integrationSettings)
24
25
  : [];
26
+ req.logInfo('Marking files as unsynced');
27
+ if (files.length > 0) {
28
+ files = yield (0, files_1.markUnsyncedFiles)({
29
+ integrationId: req.crowdinContext.clientId,
30
+ crowdinId: req.crowdinContext.crowdinId,
31
+ client: req.crowdinApiClient,
32
+ files,
33
+ });
34
+ }
25
35
  req.logInfo(`Returning ${files.length} files`);
26
36
  res.send(files);
27
37
  }
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../../../types';
4
+ export default function handle(config: Config): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -12,9 +12,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const types_1 = require("../util/types");
13
13
  const util_1 = require("../../../util");
14
14
  const storage_1 = require("../../../storage");
15
- function handle() {
15
+ function handle(config) {
16
16
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
17
- const id = req.query.job_id || req.body.job_id;
17
+ var _a;
18
+ const id = req.query.jobId || req.body.jobId;
19
+ const isApi = (0, util_1.isApiRequest)(req, config);
20
+ if (isApi && !((_a = req.body) === null || _a === void 0 ? void 0 : _a.projectId)) {
21
+ res.send({
22
+ error: 'Project id is require',
23
+ });
24
+ return;
25
+ }
18
26
  if (!id) {
19
27
  req.logInfo('Job id is absent');
20
28
  res.status(400).send('Job id is required');
@@ -35,13 +35,33 @@ function getHumanETA(ms) {
35
35
  }
36
36
  function handle(config) {
37
37
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
38
- const id = req.query.job_id || req.body.job_id;
38
+ var _a;
39
+ const id = req.query.jobId || req.body.jobId;
40
+ const isApi = (0, util_1.isApiRequest)(req, config);
41
+ if (isApi && !((_a = req.body) === null || _a === void 0 ? void 0 : _a.projectId)) {
42
+ res.send({
43
+ error: 'Project id is require',
44
+ });
45
+ return;
46
+ }
39
47
  if (!id) {
40
48
  req.logInfo('Get active jobs');
41
49
  const jobs = yield (0, storage_1.getStorage)().getActiveJobs({
42
50
  integrationId: req.crowdinContext.clientId,
43
51
  crowdinId: req.crowdinContext.crowdinId,
44
52
  });
53
+ if (isApi && jobs) {
54
+ const filteredJobs = jobs.map((job) => ({
55
+ id: job.id,
56
+ progress: job.progress,
57
+ status: job.status,
58
+ title: job.title,
59
+ }));
60
+ req.logInfo(`Returning active filtered jobs info ${JSON.stringify(filteredJobs, null, 2)}`);
61
+ res.send(filteredJobs);
62
+ return;
63
+ }
64
+ req.logInfo(`Returning active jobs info ${JSON.stringify(jobs, null, 2)}`);
45
65
  res.send(jobs);
46
66
  return;
47
67
  }
@@ -51,6 +71,17 @@ function handle(config) {
51
71
  job.eta = ((Date.now() - job.createdAt) / job.progress) * (100 - job.progress);
52
72
  job.info = getHumanETA(job.eta) + (job.info ? `\n${job.info}` : '');
53
73
  }
74
+ if (isApi && job) {
75
+ const filteredJob = {
76
+ id: job.id,
77
+ progress: job.progress,
78
+ status: job.status,
79
+ title: job.title,
80
+ };
81
+ req.logInfo(`Returning filtered job info ${JSON.stringify(filteredJob, null, 2)}`);
82
+ res.send([filteredJob]);
83
+ return;
84
+ }
54
85
  if (job && (job === null || job === void 0 ? void 0 : job.updatedAt) && Date.now() - job.updatedAt >= MINUTES * 60 * 1000) {
55
86
  const context = req.crowdinContext;
56
87
  const projectId = context.jwtPayload.context.project_id;
@@ -15,10 +15,7 @@ const logger_1 = require("../../../util/logger");
15
15
  function handle() {
16
16
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
17
17
  var _a;
18
- let userTimezone = (yield req.crowdinApiClient.usersApi.getAuthenticatedUser()).data.timezone;
19
- if (!userTimezone) {
20
- userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
21
- }
18
+ const userTimezone = (yield req.crowdinApiClient.usersApi.getAuthenticatedUser()).data.timezone;
22
19
  req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId}`);
23
20
  const userErrors = (_a = (yield (0, storage_1.getStorage)().getAllUserErrors(req.crowdinContext.crowdinId, req.crowdinContext.clientId))) === null || _a === void 0 ? void 0 : _a.map((userError) => {
24
21
  if (!(0, util_1.isJson)(userError.data)) {
@@ -28,16 +25,7 @@ function handle() {
28
25
  });
29
26
  }
30
27
  const date = new Date(+userError.createdAt);
31
- // Format the date as 'MMM DD, YYYY HH:mm'
32
- const formattedDate = new Intl.DateTimeFormat('en-US', {
33
- year: 'numeric',
34
- month: 'short',
35
- day: 'numeric',
36
- hour: '2-digit',
37
- minute: '2-digit',
38
- hour12: false,
39
- timeZone: userTimezone,
40
- }).format(date);
28
+ const formattedDate = (0, util_1.getFormattedDate)({ date, userTimezone });
41
29
  return Object.assign(Object.assign({}, userError), { formattedDate });
42
30
  });
43
31
  req.logInfo(`Returning ${userErrors === null || userErrors === void 0 ? void 0 : userErrors.length} user errors`);
@@ -99,7 +99,7 @@ function register({ config, app }) {
99
99
  optional: false,
100
100
  checkSubscriptionExpiration: true,
101
101
  moduleKey: integrationLogic.key,
102
- }), (0, job_cancel_1.default)());
102
+ }), (0, job_cancel_1.default)(config));
103
103
  app.post('/api/settings', (0, crowdin_client_1.default)({
104
104
  config,
105
105
  optional: false,
@@ -289,7 +289,8 @@ export interface File {
289
289
  parentId?: string;
290
290
  nodeType?: IntegrationTreeElementType;
291
291
  customContent?: string;
292
- labels?: LabelTreeElement;
292
+ labels?: LabelTreeElement[];
293
+ failed?: boolean;
293
294
  }
294
295
  export interface Folder {
295
296
  id: string;
@@ -297,7 +298,7 @@ export interface Folder {
297
298
  parentId?: string;
298
299
  nodeType?: IntegrationTreeElementType;
299
300
  customContent?: string;
300
- labels?: LabelTreeElement;
301
+ labels?: LabelTreeElement[];
301
302
  }
302
303
  export type TreeItem = File | Folder;
303
304
  /**
@@ -390,6 +391,7 @@ type LabelTreeElementType = 'primary' | 'secondary' | 'success' | 'warning' | 'i
390
391
  export interface LabelTreeElement {
391
392
  text: string;
392
393
  type?: LabelTreeElementType;
394
+ tooltip?: string;
393
395
  color?: string;
394
396
  }
395
397
  export declare enum Provider {
@@ -1,5 +1,12 @@
1
1
  import { ExtendedResult, IntegrationFile, IntegrationLogic, IntegrationRequest, SkipIntegrationNodes, TreeItem } from '../types';
2
2
  import { JobClient } from './types';
3
+ import Crowdin from '@crowdin/crowdin-api-client';
3
4
  export declare function skipFilesByRegex(files: TreeItem[] | undefined, skipIntegrationNodes?: SkipIntegrationNodes): TreeItem[];
4
5
  export declare function expandFilesTree(nodes: IntegrationFile[], req: IntegrationRequest, integration: IntegrationLogic, job?: JobClient): Promise<IntegrationFile[]>;
5
6
  export declare function isExtendedResultType<T>(data?: T | ExtendedResult<T>): data is ExtendedResult<T>;
7
+ export declare function markUnsyncedFiles({ integrationId, crowdinId, client, files, }: {
8
+ integrationId: string;
9
+ crowdinId: string;
10
+ client: Crowdin;
11
+ files?: TreeItem[];
12
+ }): Promise<TreeItem[]>;
@@ -9,8 +9,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.isExtendedResultType = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
12
+ exports.markUnsyncedFiles = exports.isExtendedResultType = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
13
13
  const types_1 = require("./types");
14
+ const storage_1 = require("../../../storage");
15
+ const util_1 = require("../../../util");
14
16
  function skipFilesByRegex(files, skipIntegrationNodes) {
15
17
  if (!Array.isArray(files)) {
16
18
  return [];
@@ -55,3 +57,44 @@ function isExtendedResultType(data) {
55
57
  return !!dataTyped && !Array.isArray(dataTyped);
56
58
  }
57
59
  exports.isExtendedResultType = isExtendedResultType;
60
+ function isFileLeaf(file) {
61
+ return file.nodeType === '1' || !!file.type;
62
+ }
63
+ function markUnsyncedFiles({ integrationId, crowdinId, client, files, }) {
64
+ return __awaiter(this, void 0, void 0, function* () {
65
+ if (!Array.isArray(files)) {
66
+ return [];
67
+ }
68
+ const unsyncedFilesData = yield (0, storage_1.getStorage)().getUnsyncedFiles({ integrationId, crowdinId });
69
+ let unsyncedFiles = (unsyncedFilesData === null || unsyncedFilesData === void 0 ? void 0 : unsyncedFilesData.files)
70
+ ? JSON.parse(unsyncedFilesData === null || unsyncedFilesData === void 0 ? void 0 : unsyncedFilesData.files)
71
+ : [];
72
+ const fileIds = files.filter((file) => isFileLeaf(file)).map((file) => `${file.id}`);
73
+ const idsToRemove = unsyncedFiles.filter((file) => !fileIds.includes(file.id)).map((file) => file.id);
74
+ unsyncedFiles = unsyncedFiles.filter((file) => !idsToRemove.includes(file.id));
75
+ const userTimezone = (yield client.usersApi.getAuthenticatedUser()).data.timezone;
76
+ yield (0, storage_1.getStorage)().updateUnsyncedFiles({
77
+ integrationId,
78
+ crowdinId,
79
+ files: JSON.stringify(unsyncedFiles),
80
+ });
81
+ files = files.map((file) => {
82
+ const unsynced = unsyncedFiles.find((unsyncedFile) => unsyncedFile.id === file.id);
83
+ if (unsynced && isFileLeaf(file)) {
84
+ const formattedDate = (0, util_1.getFormattedDate)({ date: new Date(+unsynced.updatedAt), userTimezone });
85
+ file.labels = [
86
+ {
87
+ text: `Sync failed ${formattedDate}`,
88
+ type: 'warning',
89
+ tooltip: unsynced === null || unsynced === void 0 ? void 0 : unsynced.message,
90
+ },
91
+ ...(file.labels ? file.labels : []),
92
+ ];
93
+ file.failed = true;
94
+ }
95
+ return file;
96
+ });
97
+ return files;
98
+ });
99
+ }
100
+ exports.markUnsyncedFiles = markUnsyncedFiles;
@@ -149,7 +149,6 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
149
149
  }
150
150
  return translation;
151
151
  }),
152
- // translationUploaded: async ({ fileId, languageId, etag }) => {
153
152
  translationUploaded: ({ fileId, translationParams }) => __awaiter(this, void 0, void 0, function* () {
154
153
  if (!isDbStore) {
155
154
  translationParams.forEach(({ languageId, etag }) => (store[integrationId][crowdinId][fileId][languageId] = { etag }));
@@ -176,6 +175,39 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
176
175
  }
177
176
  yield Promise.all([...updates, ...inserts]);
178
177
  }),
178
+ markFilesAsUnsynced: ({ files }) => __awaiter(this, void 0, void 0, function* () {
179
+ const unsyncedFilesData = yield storage.getUnsyncedFiles({ integrationId, crowdinId });
180
+ const updatedFileRecords = files.map((file) => ({
181
+ id: file.fileId,
182
+ message: file.error,
183
+ updatedAt: `${Date.now()}`,
184
+ }));
185
+ if (unsyncedFilesData) {
186
+ const existingUnsyncedFiles = (unsyncedFilesData === null || unsyncedFilesData === void 0 ? void 0 : unsyncedFilesData.files) ? JSON.parse(unsyncedFilesData.files) : [];
187
+ const uniqueFilesMap = new Map([...existingUnsyncedFiles, ...updatedFileRecords].map((file) => [file.id, file]));
188
+ const uniqueFiles = Array.from(uniqueFilesMap.values());
189
+ yield storage.updateUnsyncedFiles({ integrationId, crowdinId, files: JSON.stringify(uniqueFiles) });
190
+ }
191
+ else {
192
+ yield storage.saveUnsyncedFiles({
193
+ integrationId,
194
+ crowdinId,
195
+ files: JSON.stringify(updatedFileRecords),
196
+ });
197
+ }
198
+ }),
199
+ unmarkFilesAsUnsynced: ({ files }) => __awaiter(this, void 0, void 0, function* () {
200
+ const unsyncedFilesData = yield storage.getUnsyncedFiles({ integrationId, crowdinId });
201
+ if (unsyncedFilesData) {
202
+ const existingUnsyncedFiles = (unsyncedFilesData === null || unsyncedFilesData === void 0 ? void 0 : unsyncedFilesData.files) ? JSON.parse(unsyncedFilesData.files) : [];
203
+ const updatedFiles = existingUnsyncedFiles.filter((file) => !files.some((unmarkFile) => unmarkFile.fileId === file.id));
204
+ yield storage.updateUnsyncedFiles({
205
+ integrationId,
206
+ crowdinId,
207
+ files: JSON.stringify(updatedFiles),
208
+ });
209
+ }
210
+ }),
179
211
  };
180
212
  try {
181
213
  const data = yield jobCallback(job);