@manyos/smileconnect-api 1.52.0 → 1.52.2
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/app.js +7 -0
- package/conf/clients.json +2 -1
- package/controller/ticketCIRelationController.js +4 -2
- package/docs/releases.md +7 -0
- package/package.json +1 -1
- package/routes/appConfigRoutes.js +50 -0
- package/routes/taskRoutes.js +3 -3
- package/util/config.js +219 -1
- package/util/constants.js +2 -0
- package/util/mappingUtil.js +14 -0
package/app.js
CHANGED
|
@@ -181,6 +181,13 @@ app.use('/v1/health', function (req, res, next) {
|
|
|
181
181
|
res.json({status:"ok"})
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
+
//health check
|
|
185
|
+
app.use('/v1/openapi/:clientId', async function (req, res, next) {
|
|
186
|
+
const clientId = req.params.clientId;
|
|
187
|
+
const spec = await config.getDesignPackage(clientId)
|
|
188
|
+
res.json(spec)
|
|
189
|
+
})
|
|
190
|
+
|
|
184
191
|
const maxFilesize = process.env.MAX_FILESIZE || 5;
|
|
185
192
|
|
|
186
193
|
app.use(fileUpload({
|
package/conf/clients.json
CHANGED
|
@@ -390,13 +390,15 @@ async function getOppositeAssocTypeByType(assocType) {
|
|
|
390
390
|
async function getOppositeAssocTypeByCode(assocCode) {
|
|
391
391
|
const assocTypes = await getAssocTypes();
|
|
392
392
|
return assocTypes.find(assoc => {
|
|
393
|
-
return assoc.assocCode === assocCode
|
|
393
|
+
return assoc.assocCode === assocCode;å
|
|
394
394
|
});
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
function doesCIRelationExists(ticketRelations, newRelation) {
|
|
398
|
+
log.debug('check for relation old', ticketRelations)
|
|
399
|
+
log.debug('check for relation new', newRelation)
|
|
398
400
|
const relationFound = ticketRelations.find(relation => {
|
|
399
|
-
return (relation.ciId === newRelation.ciId &&
|
|
401
|
+
return ((relation.ciId === newRelation.ciId || relation.ciId === newRelation.ciReconId || relation.ciReconId === newRelation.ciId) &&
|
|
400
402
|
relation.relationType === newRelation.relationType);
|
|
401
403
|
});
|
|
402
404
|
if (relationFound) {
|
package/docs/releases.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
3
|
## API
|
|
4
|
+
|
|
5
|
+
### 1.52.2 - 16.11.11
|
|
6
|
+
Fix issue: Inbound Events Names missed 'Worklog' for Task Worklogs
|
|
7
|
+
|
|
8
|
+
### 1.52.1 - 12.11.11
|
|
9
|
+
Fix issue: Task CI relations might be removed on duplicate updates
|
|
10
|
+
|
|
4
11
|
### 1.52.0 - 09.11.21
|
|
5
12
|
Allow 16k header
|
|
6
13
|
|
package/package.json
CHANGED
|
@@ -377,6 +377,27 @@ module.exports = (function() {
|
|
|
377
377
|
}
|
|
378
378
|
});
|
|
379
379
|
|
|
380
|
+
/*routes.get('/clients/:id/designPackage',
|
|
381
|
+
async function (req, res, next) {
|
|
382
|
+
const id = req.params.id;
|
|
383
|
+
setEventData(
|
|
384
|
+
req,
|
|
385
|
+
CONSTANTS.EVENT_BASE_APC,
|
|
386
|
+
CONSTANTS.EVENT_ACTION_QUERY,
|
|
387
|
+
CONSTANTS.FORM_APC_CLIENTS,
|
|
388
|
+
id
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const clientDesignPackage = await config.getDesignPackage(id)
|
|
393
|
+
req.result = clientDesignPackage
|
|
394
|
+
next();
|
|
395
|
+
} catch (error) {
|
|
396
|
+
next(error);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
*/
|
|
400
|
+
|
|
380
401
|
routes.get('/clients', isAuthorizedAdmin,
|
|
381
402
|
function (req, res, next) {
|
|
382
403
|
const id = req.params.id;
|
|
@@ -450,5 +471,34 @@ module.exports = (function() {
|
|
|
450
471
|
}
|
|
451
472
|
});
|
|
452
473
|
|
|
474
|
+
routes.get('/forms/:id/fields', isAuthorizedAdmin,
|
|
475
|
+
async function (req, res, next) {
|
|
476
|
+
const id = req.params.id;
|
|
477
|
+
const nameOnly = req.query.nameOnly;
|
|
478
|
+
setEventData(
|
|
479
|
+
req,
|
|
480
|
+
CONSTANTS.EVENT_BASE_APC,
|
|
481
|
+
CONSTANTS.EVENT_ACTION_QUERY,
|
|
482
|
+
CONSTANTS.FORM_APC_FORMS_FIELDS,
|
|
483
|
+
id
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
let fields = await config.getFields(id);
|
|
488
|
+
if (nameOnly === 'true') {
|
|
489
|
+
fields = fields.map(x => x.name);
|
|
490
|
+
}
|
|
491
|
+
if (fields && Array.isArray(fields)) {
|
|
492
|
+
fields.sort();
|
|
493
|
+
}
|
|
494
|
+
req.result = {
|
|
495
|
+
"data": fields
|
|
496
|
+
};
|
|
497
|
+
next();
|
|
498
|
+
} catch (error) {
|
|
499
|
+
next(error);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
453
503
|
return routes;
|
|
454
504
|
})();
|
package/routes/taskRoutes.js
CHANGED
|
@@ -158,7 +158,7 @@ module.exports = (function () {
|
|
|
158
158
|
req.globalScriptParams.id2 = taskId
|
|
159
159
|
eventLog.setEventData(
|
|
160
160
|
req,
|
|
161
|
-
req.parentEventBase + '_' + CONSTANTS.
|
|
161
|
+
req.parentEventBase + '_' + CONSTANTS.EVENT_BASE_TAS_WORKLOG,
|
|
162
162
|
CONSTANTS.EVENT_ACTION_QUERY,
|
|
163
163
|
CONSTANTS.FORM_TASK_WORKLOG,
|
|
164
164
|
req.parentId,
|
|
@@ -184,7 +184,7 @@ module.exports = (function () {
|
|
|
184
184
|
req.globalScriptParams.id2 = taskId
|
|
185
185
|
eventLog.setEventData(
|
|
186
186
|
req,
|
|
187
|
-
req.parentEventBase + '_' + CONSTANTS.
|
|
187
|
+
req.parentEventBase + '_' + CONSTANTS.EVENT_BASE_TAS_WORKLOG,
|
|
188
188
|
CONSTANTS.EVENT_ACTION_CREATE,
|
|
189
189
|
CONSTANTS.FORM_TASK_WORKLOG,
|
|
190
190
|
req.parentId,
|
|
@@ -228,7 +228,7 @@ module.exports = (function () {
|
|
|
228
228
|
req.globalScriptParams.id3 = worklogId
|
|
229
229
|
eventLog.setEventData(
|
|
230
230
|
req,
|
|
231
|
-
req.parentEventBase + '_' + CONSTANTS.
|
|
231
|
+
req.parentEventBase + '_' + CONSTANTS.EVENT_BASE_TAS_WORKLOG,
|
|
232
232
|
CONSTANTS.EVENT_ACTION_QUERY,
|
|
233
233
|
CONSTANTS.FORM_TASK_WORKLOG,
|
|
234
234
|
req.parentId,
|
package/util/config.js
CHANGED
|
@@ -6,6 +6,7 @@ const log = require('@manyos/logger').setupLog('SMILEconnect_' + path.basename(_
|
|
|
6
6
|
|
|
7
7
|
const clientConfigFile = 'conf/clients.json';
|
|
8
8
|
const arquery = require('../util/arquery');
|
|
9
|
+
const mappingUtil = require('../util/mappingUtil');
|
|
9
10
|
const CacheService = require ('../util/cache.service');
|
|
10
11
|
|
|
11
12
|
const CONSTANTS = require('./constants');
|
|
@@ -78,6 +79,15 @@ async function setCustomFormMapping(mapping, objectType) {
|
|
|
78
79
|
return getCustomFormMapping(objectType)
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function getDeprecatedMappingAsCustom(objectType) {
|
|
83
|
+
const deprecatedMapping = getMapping(objectType)
|
|
84
|
+
const mapping = {}
|
|
85
|
+
deprecatedMapping.forEach(item => {
|
|
86
|
+
mapping[item.oldName] = item.newName
|
|
87
|
+
})
|
|
88
|
+
return mapping
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
function getMapping(objectType) {
|
|
82
92
|
const configFile = 'conf/mapping.json';
|
|
83
93
|
log.debug('start to read config from file', configFile);
|
|
@@ -297,6 +307,213 @@ async function checkConfig() {
|
|
|
297
307
|
log.info('check config done');
|
|
298
308
|
}
|
|
299
309
|
|
|
310
|
+
async function getDesignPackage(clientId) {
|
|
311
|
+
const info = {
|
|
312
|
+
title: `SMILEconnect Interface for ${clientId}`,
|
|
313
|
+
description: `This document contains the SMILEconnect OpenAPI specification for the client ${clientId}`,
|
|
314
|
+
version: Date.now(),
|
|
315
|
+
contact: {
|
|
316
|
+
email: 'mail@manyos.it'
|
|
317
|
+
},
|
|
318
|
+
termsOfService: 'https://manyos.it'
|
|
319
|
+
}
|
|
320
|
+
const servers = [
|
|
321
|
+
{
|
|
322
|
+
url: "https://url123",
|
|
323
|
+
description: "dev"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
url: "https://url12345",
|
|
327
|
+
description: "prod"
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
const clientConfig = await getClientConfig(clientId)
|
|
331
|
+
const schemas = {}
|
|
332
|
+
let paths = {}
|
|
333
|
+
const keys = Object.keys(ticketConfig)
|
|
334
|
+
for (let x=0; x<keys.length; x++) {
|
|
335
|
+
const key = keys[x]
|
|
336
|
+
const config = ticketConfig[key]
|
|
337
|
+
const clientObjectConfig = clientConfig[config.requestType]
|
|
338
|
+
if (clientObjectConfig) {
|
|
339
|
+
{
|
|
340
|
+
const mapping = getDeprecatedMappingAsCustom(config.requestType)
|
|
341
|
+
schemas[config.requestType] = await getObjectSchema(clientObjectConfig, mapping, config.forms.regular)
|
|
342
|
+
paths = {...paths, ... await getPathDef(config.assocTicketType, config.baseURI, config.requestType)}
|
|
343
|
+
}
|
|
344
|
+
if (config.requestTypeWorkLog) {
|
|
345
|
+
const mapping = getDeprecatedMappingAsCustom(config.requestTypeWorkLog)
|
|
346
|
+
schemas[config.requestTypeWorkLog] = await getObjectSchema(clientConfig[config.requestTypeWorkLog], mapping, config.forms.workLog)
|
|
347
|
+
paths = {...paths, ... await getPathDef(config.assocTicketType, config.baseURI + '/{ticketId}/worklogs', config.requestTypeWorkLog, true, true)}
|
|
348
|
+
}
|
|
349
|
+
if (config.requestTemplate) {
|
|
350
|
+
const mapping = getDeprecatedMappingAsCustom(config.requestTemplate)
|
|
351
|
+
schemas[config.requestTemplate] = await getObjectSchema(clientConfig[config.requestTemplate], mapping, config.forms.template)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const customFormMappings = await getCustomFormMapping()
|
|
356
|
+
const customKeys = Object.keys(customFormMappings)
|
|
357
|
+
for (let x=0; x<customKeys.length; x++) {
|
|
358
|
+
const key = customKeys[x]
|
|
359
|
+
const formName = customFormMappings[key].formName
|
|
360
|
+
const clientObjectConfig = clientConfig[`custom_${formName}`]
|
|
361
|
+
log.debug('key', key, clientObjectConfig)
|
|
362
|
+
if (clientObjectConfig) {
|
|
363
|
+
schemas[key] = await getObjectSchema(clientObjectConfig, customFormMappings[key].mapping, formName)
|
|
364
|
+
paths = {...paths, ... await getPathDef(key, '/v1/customForms/' + key, key)}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return {openapi: "3.0.1", info, servers, paths, components: {schemas}}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function getPathDef(objectName, baseUri, tag, isSubTicket, suppressUpdate, suppressCreate) {
|
|
371
|
+
const ticketIdParam = {
|
|
372
|
+
name: "ticketId",
|
|
373
|
+
description: "id of the parent object to retrieve",
|
|
374
|
+
schema: {
|
|
375
|
+
"type": "string"
|
|
376
|
+
},
|
|
377
|
+
in: "path",
|
|
378
|
+
required: true
|
|
379
|
+
}
|
|
380
|
+
const parameters = [
|
|
381
|
+
{
|
|
382
|
+
name: "id",
|
|
383
|
+
description: "id of the object to retrieve",
|
|
384
|
+
schema: {
|
|
385
|
+
"type": "string"
|
|
386
|
+
},
|
|
387
|
+
in: "path",
|
|
388
|
+
required: true
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
if (isSubTicket) {
|
|
392
|
+
parameters.push(ticketIdParam)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const pathDef = {}
|
|
396
|
+
pathDef[`${baseUri}/{id}`] = {
|
|
397
|
+
parameters,
|
|
398
|
+
get: {
|
|
399
|
+
tags: [tag],
|
|
400
|
+
summary: `Get a single ${objectName} object by id`,
|
|
401
|
+
description: "A single object is returned in the data attribute",
|
|
402
|
+
parameters: [],
|
|
403
|
+
responses: await getApiResponse(tag, true)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!suppressUpdate) {
|
|
407
|
+
pathDef[`${baseUri}/{id}`].put = {
|
|
408
|
+
tags: [tag],
|
|
409
|
+
summary: `Update an object of type ${objectName}`,
|
|
410
|
+
description: "A single object is returned in the data attribute",
|
|
411
|
+
parameters: [],
|
|
412
|
+
responses: await getApiResponse(tag, false)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
pathDef[`${baseUri}`] = {
|
|
417
|
+
get: {
|
|
418
|
+
tags: [tag],
|
|
419
|
+
summary: `Get a list of object of type ${objectName}`,
|
|
420
|
+
description: "An array ob objects is returned in the data attribute",
|
|
421
|
+
parameters: [],
|
|
422
|
+
responses: await getApiResponse(tag, false)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (!suppressCreate) {
|
|
426
|
+
pathDef[`${baseUri}`].post = {
|
|
427
|
+
tags: [tag],
|
|
428
|
+
summary: `Create a new object of type ${objectName}`,
|
|
429
|
+
description: "A single object is returned in the data attribute",
|
|
430
|
+
parameters: [],
|
|
431
|
+
responses: await getApiResponse(tag, false)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (isSubTicket) {
|
|
435
|
+
pathDef[`${baseUri}`].parameters = [ticketIdParam]
|
|
436
|
+
}
|
|
437
|
+
return pathDef
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function getApiResponse(schema, single) {
|
|
441
|
+
responses = {
|
|
442
|
+
"200": {
|
|
443
|
+
description: "successful operation",
|
|
444
|
+
content: {
|
|
445
|
+
"application/json": {
|
|
446
|
+
schema: {
|
|
447
|
+
type: "object",
|
|
448
|
+
properties: {
|
|
449
|
+
data: {}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (!single) {
|
|
457
|
+
responses["200"].content["application/json"].schema.properties.data = {
|
|
458
|
+
type: "array",
|
|
459
|
+
items: {
|
|
460
|
+
"$ref": `#/components/schemas/${schema}`
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
responses["200"].content["application/json"].schema.properties.data = {
|
|
465
|
+
"$ref": `#/components/schemas/${schema}`
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return responses
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function getObjectSchema(clientObjectConfig, mapping, formName) {
|
|
472
|
+
const fields = await getFields(formName)
|
|
473
|
+
const required = []
|
|
474
|
+
const properties = {}
|
|
475
|
+
log.debug('clientObjConf', clientObjectConfig)
|
|
476
|
+
clientObjectConfig.fields.forEach(field => {
|
|
477
|
+
const mappedName = mapping[field]
|
|
478
|
+
const fieldDef = {}
|
|
479
|
+
const fieldDetails = fields.find(item => item.name === field)
|
|
480
|
+
|
|
481
|
+
if (fieldDetails) {
|
|
482
|
+
fieldDef.type = fieldDetails.type
|
|
483
|
+
if (fieldDetails.entryMode === 'Required') {
|
|
484
|
+
required.push(mappedName)
|
|
485
|
+
}
|
|
486
|
+
if(fieldDetails.fieldLimit) {
|
|
487
|
+
fieldDef.maxLength = fieldDetails.fieldLimit.maxLength
|
|
488
|
+
}
|
|
489
|
+
if (fieldDetails.type === 'SelectionField') {
|
|
490
|
+
fieldDef.type = 'string'
|
|
491
|
+
if (fieldDetails.fieldLimit && fieldDetails.fieldLimit.values) {
|
|
492
|
+
const enumVals = fieldDetails.fieldLimit.values.map(item => item.enumItemName)
|
|
493
|
+
fieldDef.enum = enumVals
|
|
494
|
+
} else if (fieldDetails.fieldLimit && fieldDetails.fieldLimit.valueMapping) {
|
|
495
|
+
const enumVals = Object.values(fieldDetails.fieldLimit.valueMapping)
|
|
496
|
+
fieldDef.enum = enumVals
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
} else if (fieldDetails.type === 'CharacterField') {
|
|
500
|
+
fieldDef.type = 'string'
|
|
501
|
+
} else if (fieldDetails.type === 'DateTimeField') {
|
|
502
|
+
fieldDef.type = 'string'
|
|
503
|
+
fieldDef.format = 'date-time'
|
|
504
|
+
} else if (fieldDetails.type === 'DateField') {
|
|
505
|
+
fieldDef.format = 'date'
|
|
506
|
+
} else if (fieldDetails.type === 'DecimalField') {
|
|
507
|
+
fieldDef.type = 'number'
|
|
508
|
+
}
|
|
509
|
+
//fieldDef.details = fieldDetails
|
|
510
|
+
}
|
|
511
|
+
properties[mappedName] = fieldDef
|
|
512
|
+
})
|
|
513
|
+
//mappedFields.push(fields)
|
|
514
|
+
return {required, properties}
|
|
515
|
+
}
|
|
516
|
+
|
|
300
517
|
function getForms() {
|
|
301
518
|
log.debug('get forms');
|
|
302
519
|
const key = 'forms';
|
|
@@ -416,5 +633,6 @@ module.exports = {
|
|
|
416
633
|
getFields,
|
|
417
634
|
ticketConfig,
|
|
418
635
|
getCustomFormMapping,
|
|
419
|
-
setCustomFormMapping
|
|
636
|
+
setCustomFormMapping,
|
|
637
|
+
getDesignPackage
|
|
420
638
|
};
|
package/util/constants.js
CHANGED
package/util/mappingUtil.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
+
const {getFields} = require("./config");
|
|
2
3
|
const log = require('@manyos/logger').setupLog('SMILEconnect_' + path.basename(__filename));
|
|
3
4
|
|
|
4
5
|
function applyMapping2Remedy(entryData, mapping, constants, fields) {
|
|
@@ -91,6 +92,19 @@ function getFieldsForCreate(mapping, mappingNew, clientFields) {
|
|
|
91
92
|
return targetList;
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
//get the allowed fields for a POST Action based on a PUT/GET Action
|
|
96
|
+
/*function mapField(mapping, remedyField) {
|
|
97
|
+
const mapped = mapping.find(element => {
|
|
98
|
+
return element.oldName === remedyField;
|
|
99
|
+
});
|
|
100
|
+
log.debug(mapped)
|
|
101
|
+
if (mapped) {
|
|
102
|
+
return mapped.newName
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return remedyField;
|
|
106
|
+
}*/
|
|
107
|
+
|
|
94
108
|
function applyCustomFormMapping(object, mapping, requestIdField) {
|
|
95
109
|
//Apply mapping
|
|
96
110
|
Object.keys(mapping).forEach(remedyName => {
|