@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 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
@@ -332,7 +332,8 @@
332
332
  "Work Order ID",
333
333
  "Status",
334
334
  "InstanceId",
335
- "WO Type Field 2",
335
+ "WO Type Field 02",
336
+ "WO Type Field 10",
336
337
  "Categorization Tier 1",
337
338
  "Categorization Tier 2",
338
339
  "Categorization Tier 3",
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manyos/smileconnect-api",
3
- "version": "1.52.0",
3
+ "version": "1.52.2",
4
4
  "description": "A proxy and abstraction layer for BMCs IT Service Management Suite",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -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
  })();
@@ -158,7 +158,7 @@ module.exports = (function () {
158
158
  req.globalScriptParams.id2 = taskId
159
159
  eventLog.setEventData(
160
160
  req,
161
- req.parentEventBase + '_' + CONSTANTS.EVENT_BASE_TAS,
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.EVENT_BASE_TAS,
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.EVENT_BASE_TAS,
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
@@ -68,6 +68,8 @@ module.exports = {
68
68
 
69
69
  EVENT_BASE_TAS: "TAS",
70
70
 
71
+ EVENT_BASE_TAS_WORKLOG: "TAS_Worklog",
72
+
71
73
  EVENT_ACTION_QUERY: 'Query',
72
74
  EVENT_ACTION_CREATE: 'Create',
73
75
  EVENT_ACTION_MODIFY: 'Modify',
@@ -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 => {