Outbound Processors

🚧

Important Note!

We recommend to use the writeToLdif Inbound Processors to export data as LDIF.

Overview

Outbound Processors reads data from SAP LeanIX and exports it to an LDIF. Please see the Integration API main page for more information on LDIF's including a detailed breakdown of the different key-value pairs.

When creating an outbound configuration, the user can retrieve an LDIF after executing a synchronization run. The content of the produced LDIF will depend on the configured data processors. Only outbound processors are available in outbound configurations.

Each Outbound Data Processors will always have the same output fields (targeting the LDIF standard) but depending on type we will see other filter elements and have different data available in scope of the JUEL parsers

Outbound processors will always create valid LDIF files. This includes that mandatory LDIF fields in the header and for each data object (type, id) are always present. This information is automatically taken from the outbound configuration.

📘

Processor Naming

The processor name should not include any reserved characters which are as operators e.g. +,*,>,= …

General

The processor configurations (outbound Data Processors) need to contain the names of the Fact Sheet fields that will be written (for performance reasons, only selected fields will be read) and names of the relations including the requested fields of the relations. For Tags users can filter by tag group and retrieve all tags in this group. For Subscriptions, the user will filter by the type of a subscription.

Setting the Scope

The outbound configuration needs to contain a scope. The scope defines the set of Fact Sheets that will be looked at when iterating over the configured outbound processors and creating content in the resulting LDIF. However, while the scope section needs to exist, it can be left completely empty as the example above shows, this means that it will look at all Fact Sheets.

The scope defined in the below example will iterate over all Fact Sheets. The UI provides a visual interface (accessible via the Set Scope button) which allows for the selection of the Fact Sheet data required and sets this data as in "scope" into the configuration. The default scope used for all processors will be defined in the global section. Each processor may contain a scope section to overwrite the global scope settings.

Scope configuration:

{
 "scope": {},
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the processor",
   "scope": {
    "ids": [],
    "facetFilters": [
     {
      "keys": [
       "Application"
      ],
      "facetKey": "FactSheetTypes",
      "operator": "OR"
     }
    ]
   }
  }
 ]
}
2712

Set Scope using the "Set Scope" button or leave the Scope blank in order to leave the search unrestricted.

Setting the Fields

The processor needs the "fields" section to define the names of the fields that will be read from each Fact Sheet and can then be used in the output section using "lx.factsheet.fieldName".

The "description" field was marked "optional". This means in case a Fact Sheet does not have any description, there will be no warning generated but the Description key silently omitted as it was configured to be expected that description may be missing.

The above configuration leads to a resulting LDIF similar to this:

{
 "scope": {
  "facetFilters": [],
  "ids": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the processor",
   "enabled": true,
   "fields": [
    "createdAt",
    "description"
   ],
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "Description"
     },
     "values": [
      {
       "expr": "${lx.factsheet.description}"
      }
     ],
     "optional": true
    },
    {
     "key": {
      "expr": "creationDateTime"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.createdAt}"
      }
     ]
    }
   ]
  }
 ]
}
{
	"results": {
		"connectorType": "Test connector",
		"connectorId": "--",
		"connectorVersion": "1.0.0",
		"processingDirection": "INBOUND",
		"processingMode": "PARTIAL",
		"lxWorkspace": null,
		"lxVersion": "1.0.0",
		"description": "The resulting LDIF of the outbound run c86faf41-cac5-4309-a98a-bb01b8597752",
		"content": [
			{
				"type": "Application",
				"id": "fc2b0641-6172-40cf-9dbb-10e514c7e341",
				"data": {
					"Description": "",
					"creationDateTime": "2018-04-17T21:44:27.214Z"
				}
			},
			{
				"type": "Application",
				"id": "716b9d27-3df7-42ca-ae6e-e2ef788064dd",
				"data": {
					"Description": "SAP Supply Chain Management handles everything with regards to our supply chain, from planning, coordination with our supplier and customer. It is fully integrated with our Warehouse Management",
					"creationDateTime": "2018-04-17T21:44:27.214Z"
				}
			},
			{
				"type": "Application",
				"id": "1a610104-6088-498a-856f-bb6a8298bcd4",
				"data": {
					"Description": "Mailsnake is supporting newsletter mailing for different user groups for after-sales services ... announcing new features, small online training courses, ...",
					"creationDateTime": "2018-04-17T21:44:27.212Z"
				}
			},
			{
				"type": "BusinessCapability",
				"id": "58aaff75-1649-447c-bdec-01860db7026f",
				"data": {
					"Description": "",
					"creationDateTime": "2018-04-17T21:44:28.663Z"
				}
			}
		],
		"chunkInformation": {
			"firstDataObject": 0,
			"lastDataObject": 394,
			"maxDataObject": 394
		}
	},
	"warnings": [],
	"debugInfo": [],
	"debugVariables": [],
	"statistics": {
		"statusChanges": [
			{
				"status": "CREATED",
				"timestamp": "2022-03-10T07:24:01.44829307Z"
			},
			{
				"status": "PENDING",
				"timestamp": "2022-03-10T07:24:01.768868487Z"
			},
			{
				"status": "IN_PROGRESS",
				"timestamp": "2022-03-10T07:24:03.194204924Z"
			},
			{
				"status": "FINISHED",
				"timestamp": "2022-03-10T07:24:04.614733108Z"
			}
		],
		"processorStatistics": [
			{
				"processorIndex": 0,
				"processorName": "Export to LDIF",
				"networkDuration": "PT0.955467958S",
				"processingDuration": "PT0.085116706S",
				"itemsInScopeCount": 395,
				"processedContentCount": 395,
				"errorCount": 0
			}
		],
		"totalBlockingTime": "PT0S"
	},
	"resultsUrl": {}
}

Setting Filters

Filter section is where you define if the Data Processor should work on the found data object. It can be set to null to indicate no filter is being provided.

Example outbound processor with filters:

{
 "scope": {},
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "User Groups that Begin with Br",
   "processorDescription": "",
   "filter": {
    "advanced": "${lx.factsheet.name.startsWith('Br')}"
   }
  }
 ]
}
Filter TypeDetails
typeIf configured, the string is interpreted as a regular expression and matched against the "type" of the Fact Sheet
idIf configured, the string is interpreted as a regular expression and matched against the "id" d of the Fact Sheet
advancedIf configured, the field contains a JUEL expression that may evaluate to "true" for a match or "false". This filter allows to filter even for combinations of certain key and values in the Fact Sheet

📘

Data Processors provide filter capabilities to configure on which Data Object the data processor will work on (match the filter) and what Data Objects to skip (not match the filter)

Below example also includes the option to modify the choices as per external System.

Example single outbound processor:

{
 "processorType": "outboundFactSheet",
 "processorName": "<Unnamed processor>",
 "processorDescription": "",
 "filter": null,
 "enabled": true,
 "fields": [
  "nonExistingField",
  "lifecycle",
  "location",
  "createdAt",
  "businessCriticality",
  "technicalSuitabilityDescription"
 ],
 "output": [
  {
   "key": {
    "expr": "content.id"
   },
   "mode": "selectFirst",
   "values": [
    {
     "expr": "${lx.factsheet.id}"
    }
   ]
  },
  {
   "key": {
    "expr": "content.type"
   },
   "mode": "selectFirst",
   "values": [
    {
     "expr": "${lx.factsheet.type}"
    }
   ]
  },
  {
   "key": {
    "expr": "lifecycle.times"
   },
   "mode": "list",
   "values": [
    {
     "expr": "${lx.factsheet.lifecycle.endOfLife}"
    },
    {
     "expr": "${lx.factsheet.lifecycle.active}"
    },
    {
     "expr": "${lx.factsheet.lifecycle.phaseOut}"
    },
    {
     "expr": "${lx.factsheet.lifecycle.phaseIn}"
    }
   ]
  },
  {
   "key": {
    "expr": "location"
   },
   "mode": "selectFirst",
   "values": [
    {
     "expr": "${lx.factsheet.location.rawAddress}, with place id: ${lx.factsheet.location.placeId}"
    }
   ]
  },
  {
   "key": {
    "expr": "businessCriticality"
   },
   "values": [
    {
     "expr": "${lx.factsheet.businessCriticality}",
     "regexMatch": "administrativeService",
     "regexReplace": {
      "match": "^.*$",
      "replace": "Administrative Service"
     }
    },
    {
     "expr": "${lx.factsheet.businessCriticality}",
     "regexMatch": "businessOperational",
     "regexReplace": {
      "match": "^.*$",
      "replace": "Business Operational"
     }
    }
   ]
  },
  {
   "key": {
    "expr": "creationTime"
   },
   "mode": "selectFirst",
   "values": [
    {
     "expr": "${lx.factsheet.createdAt}"
    }
   ]
  },
  {
   "key": {
    "expr": "technicalSuitabilityDescription"
   },
   "mode": "selectFirst",
   "values": [
    {
     "expr": "${lx.factsheet.technicalSuitabilityDescription}"
    }
   ]
  }
 ]
}

Outbound Fact Sheet

This processor is capable writing Fact Sheet, relation, tag, document, subscription and metrics information from SAP LeanIX to LDIF.

In order to write LDIF, the scope of total Fact Sheets to read data from needs to be defined by a search. The Integration API UI allows to set the scope and add this scope to the configuration of an outbound configuration. Users then only have to regularly request the LDIF and will receive one LDIF with most current information matching the defined query. The exported data is configurable using same mechanisms as we do the inbound way.
Scopes can be defined globally for all processors or inside each processor. In case both was defined, the scope in the processor takes priority over the global used as a fallback only if the processor has not defined any.

Basic structure of the outboundFactSheet processor.

Example outboundFactSheet processor:

{
 "scope": {
  "facetFilters": [],
  "ids": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the processor",
   "enabled": true,
   "fields": [
    "lifecycle",
    "location",
    "createdAt",
    "description",
    "technicalSuitabilityDescription"
   ],
   "relations": {
    "filter": [
     "relToParent",
     "relApplicationToITComponent"
    ],
    "fields": [
     "description"
    ],
    "targetFields": [
     "displayName",
     "externalId"
    ],
    "constrainingRelations": false
   },
   "tags": {
    "groups": [
     "Other tags",
     "Cloud Transformation"
    ]
   },
   "subscriptions": {
    "types": [
     "RESPONSIBLE"
    ]
   },
   "documents": {
    "filter": ".*"
   },
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "Description"
     },
     "values": [
      {
       "expr": "${lx.factsheet.description}"
      }
     ],
     "optional": true
    },
    {
     "key": {
      "expr": "creationDateTime"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.createdAt}"
      }
     ]
    }
   ]
  }
 ]
}

Export Relations, Subscriptions and Tags

Relations

In case relations need to be available to write information about them to the LDIF, a section "relations" needs to exist. The section contains three parts: "filter", where users will configure the names of the relations they are interested in to export, the fields and the targetFields key to define what fields on the relation and what fields from the target Fact Sheet will be available in the output section. All fields on relations can be configured in the "targetFields" key which is a value within the "relations" key. A processor iterating over the target Fact Sheet types needs to be configured to read all fields.

Example configuration of relations:

{
 "relations": {
  "filter": [
   "relToParent",
   "relApplicationToITComponent"
  ],
  "fields": [
   "description"
  ],
  "targetFields": [
   "displayName",
   "externalId"
  ],
  "constrainingRelations": false
 }
}

In case relations are found, they are all available as an array in "lx.relations". An example on how to access this information fill follow below. The example above only exports the creation date of all Fact Sheets in scope.

Making information about tags and subscriptions available to be used in the output section works following the same pattern.

All Relations and Fields within Relations

In the example below all of the relations that exist in a workspace have been associated to the processor. Along with all of the attributes available within the relations, such as the "Active From" and "Active Until" fields and "Total Annual Cost". Any custom relations and or attributes can be added to the appropriate object within the LDIF.

Example processor configuration with all relations and fields within relations:

{
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the outboundFactSheet processor",
   "enabled": true,
   "fields": [
    "displayName",
    "externalId"
   ],
   "relations": {
    "filter": [
     "relToParent",
     "relToRequires",
     "relToSuccessor",
     "relApplicationToUserGroup",
     "relApplicationToDataObject",
     "relApplicationToITComponent",
     "relApplicationToProject",
     "relProviderApplicationToInterface",
     "relConsumerApplicationToInterface",
     "relApplicationToProcess",
     "relApplicationToBusinessCapability",
     "relITComponentToTechnologyStack",
     "relITComponentToUserGroup",
     "relITComponentToProvider",
     "relInterfaceToDataObject",
     "relInterfaceToITComponent",
     "relProcessToBusinessCapability",
     "relProjectToBusinessCapability",
     "relProjectToITComponent",
     "relProjectToProcess",
     "relProjectToUserGroup",
     "relProjectToProvider"
    ],
    "fields": [
     "activeFrom",
     "activeUntil",
     "description",
     "functionalSuitability",
     "numberOfUsers",
     "usageType",
     "usage",
     "costTotalAnnual",
     "technicalSuitability",
     "serviceLevel",
     "supportType",
     "resourceClassification",
     "orderNo",
     "orderedCapex",
     "orderedOpex"
    ],
    "targetFields": [
     "displayName",
     "externalId",
     "category"
    ]
   },
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "displayName"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.displayName}"
      }
     ]
    },
    {
     "key": {
      "expr": "externalId"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.externalId}"
      }
     ]
    },
    {
     "key": {
      "expr": "relations"
     },
     "mode": "list",
     "values": [
      {
       "forEach": {
        "elementOf": "${lx.relations}",
        "filter": "${true}"
       },
       "map": [
        {
         "key": "relationName",
         "value": "${integration.output.valueOfForEach.type}"
        },
        {
         "key": "object",
         "value": "${integration.output.valueOfForEach}"
        }
       ]
      }
     ]
    }
   ]
  }
 ],
 "scope": {
  "facetFilters": [],
  "ids": []
 }
}

Constraining Relations

All information about constraining relations may be made available as content in "lx.relations" to be ready for export on demand by setting the key "constrainingRelations" to true. If the key is set to false or is not defined, no information about constraining relations will be present in the variable "lx.relations"

When exporting, the following field structure may be used when iterating over relations e.g. using forEach: "integration.valueOfForEach.constrainingRelations" and write all content to a map target or filter in a forEach section applied in the "values" segment.

Example configuration of constraining relations:

{
 "constrainingRelations": {
  "relations": [
   {
    "id": "60f57ffd-6dc8-42a5-9099-283256e627ad",
    "target": {
     "id": "1c3ef333-5cc1-414d-ba3e-108af7f0ffc4",
     "type": "UserGroup",
     "leanixV3IdUserGroup": "120000002"
    }
   },
   {
    "id": "a4661164-a948-4112-82de-270c31da4297",
    "target": {
     "id": "d973d8a8-6435-4952-988d-2e29feeafd57",
     "type": "UserGroup",
     "externalId": "Europe",
     "leanixV3IdUserGroup": "120000052"
    }
   }
  ],
  "totalCounts": [
   {
    "type": "relApplicationToUserGroup",
    "totalCount": 2
   }
  ]
 }
}

Tags and Subscriptions

For Tags, users can define a list of tag groups they are interested in using for export purposes. All tags in the listed tag groups will be part of the output scope and can be used in JUEL expressions "lx.tags" and "lx.subscriptions". As for relations, the tags and subscriptions are collected in a list. This list can be processed to output LDIF in multiple ways.

Iterating with an outer "forEach" allows to create separate data objects for every tag/subscription. Using the inner "forEach" in the output section allows to add the information to the data object written for the Fact Sheet.

Below is an example that shows multiple ways to convert the information to an output LDIF. It is recommended to potentially first try an output with the full tag/subscription object. This will allow you to see and review all of the contained fields. After which point, you could update the processor by limiting the output to the very specific content that needs to be part of the result LDIF.

Example outbound processor that exports relations, tags, and subscriptions into LDIF format:

{
 "scope": {
  "facetFilters": [
   {
    "keys": [
     "Application"
    ],
    "facetKey": "FactSheetTypes",
    "operator": "OR"
   },
   {
    "keys": [
     "__any__"
    ],
    "facetKey": "lifecycle",
    "operator": "OR",
    "dateFilter": {
     "to": "2024-12-31",
     "from": "2018-01-01",
     "type": "RANGE",
     "maxDate": "2021-08-06",
     "minDate": "2010-11-07"
    }
   },
   {
    "keys": [],
    "facetKey": "relApplicationToITComponent",
    "operator": "OR"
   }
  ],
  "ids": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the outboundFactSheet processor",
   "enabled": true,
   "fields": [
    "lifecycle",
    "location",
    "createdAt",
    "technicalSuitabilityDescription",
    "description",
    "name"
   ],
   "relations": {
    "filter": [
     "relToParent",
     "relApplicationToITComponent"
    ],
    "fields": [
     "description"
    ],
    "targetFields": [
     "displayName",
     "externalId"
    ]
   },
   "tags": {
    "groups": [
     "Other tags",
     "Cloud Transformation"
    ]
   },
   "subscriptions": {
    "types": [
     "RESPONSIBLE"
    ]
   },
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "Description"
     },
     "values": [
      {
       "expr": "${lx.factsheet.description}"
      }
     ],
     "optional": true
    },
    {
     "key": {
      "expr": "lifecycleTimes"
     },
     "mode": "list",
     "values": [
      {
       "expr": "endOfLife:${lx.factsheet.lifecycle.endOfLife}",
       "regexMatch": ".*:.+$"
      },
      {
       "expr": "active:${lx.factsheet.lifecycle.active}",
       "regexMatch": ".*:.+$"
      },
      {
       "expr": "phaseOut:${lx.factsheet.lifecycle.phaseOut}",
       "regexMatch": ".*:.+$"
      },
      {
       "expr": "phaseIn:${lx.factsheet.lifecycle.phaseIn}",
       "regexMatch": ".*:.+$"
      }
     ]
    },
    {
     "key": {
      "expr": "creationDateTime"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.createdAt}"
      }
     ]
    },
    {
     "key": {
      "expr": "technicalSuitabilityDescription"
     },
     "values": [
      {
       "expr": "${lx.factsheet.technicalSuitabilityDescription}"
      }
     ]
    },
    {
     "key": {
      "expr": "relations"
     },
     "mode": "list",
     "values": [
      {
       "forEach": {
        "elementOf": "${lx.relations}",
        "filter": "${true}"
       },
       "map": [
        {
         "key": "relationName",
         "value": "${integration.output.valueOfForEach.type}"
        },
        {
         "key": "objectForFindingRelevantFields",
         "value": "${integration.output.valueOfForEach}"
        }
       ]
      }
     ]
    },
    {
     "key": {
      "expr": "tags"
     },
     "mode": "list",
     "values": [
      {
       "forEach": {
        "elementOf": "${lx.tags}",
        "filter": "${true}"
       },
       "map": [
        {
         "key": "tagGroup",
         "value": "${integration.output.valueOfForEach.tagGroup.name}"
        },
        {
         "key": "tagName",
         "value": "${integration.output.valueOfForEach.name}"
        }
       ]
      }
     ]
    },
    {
     "key": {
      "expr": "subscriptions"
     },
     "mode": "list",
     "values": [
      {
       "forEach": {
        "elementOf": "${lx.subscriptions}",
        "filter": "${true}"
       },
       "map": [
        {
         "key": "subscriptionObjectForTestOnly",
         "value": "${integration.output.valueOfForEach}"
        },
        {
         "key": "subscriptionType",
         "value": "${integration.output.valueOfForEach.type}"
        },
        {
         "key": "subscriptionRoles",
         "value": "${integration.output.valueOfForEach.roles}"
        }
       ]
      }
     ]
    }
   ]
  }
 ]
}

Below is a sample of what the lx.subscriptions object looks like, which can be accessed in the list as defined above.

Example lx.subscriptions object:

"lx.subscriptions": [
 {
  "id": "76cf02fa-c358-4653-af09-e1d9729960fa",
  "type": "RESPONSIBLE",
  "user": {
   "id": "2d9b1cbb-158f-4679-b9b8-60136a52de3e",
   "email": "[email protected]",
   "lastName": "User",
   "userName": "[email protected]",
   "firstName": "Anonymized",
   "displayName": "Anonymized User"
  },
  "roles": [
   {
    "id": "1b3e71fe-781a-4fd9-988e-56ba42b33a51",
    "name": "Business Owner"
   }
  ],
  "createdAt": "2022-12-12T09:04:40.214367Z"
 },
 {
  "id": "94fbbaf1-8c75-4cf2-a6ac-98d92e277877",
  "type": "RESPONSIBLE",
  "user": {
   "id": "8b189683-7852-4ff7-9845-f1725e324670",
   "email": "[email protected]",
   "lastName": "tuter",
   "userName": "[email protected]",
   "firstName": "abel",
   "displayName": "abel tuter"
  },
  "roles": [
   {
    "id": "60d53ae5-34f0-4f8c-8ca3-9aa7f674f41f",
    "name": "Application Owner Deputy",
    "comment": "Deputy Comment"
   }
  ],
  "createdAt": "2023-04-10T11:34:40.145262Z"
 }
]

📘

Outbound Data Processors need to have mandatory fields configured

An outbound Fact Sheet processor will fail if the mandatory output fields "content.id" and "content.type" are not defined as they are mandatory part of any LDIF.

📘

LDIF optional fields

The created file may contain more optional fields. The above displays a short version for understanding the mandatory structure. See detailed description above

Write LDIF using Modes, Map and ForEach

Produced LDIF may not only contain simple key-value pairs in the data section but lists and maps as well. The below illustrated how to configure a data processor to write these data types.

ModeDetails
listCollects all entries and writes the result as a list.
selectFirstIs the default setting, it selecting the first value that is not empty and uses it to write to the target key.

LDIF from Processor Set to Mode: List

In this case, the mode was switched to "list". The default ("selectFirst") would pick the first value that is not empty and use it to write to the target key. Mode list collects all entries and writes the result as a list.

{
 "scope": {
  "facetFilters": [],
  "ids": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "fields": [
    "lifecycle"
   ],
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "lifecycleTimes"
     },
     "mode": "list",
     "values": [
      {
       "expr": "endOfLife:${lx.factsheet.lifecycle.endOfLife}",
       "regexMatch": ".*:.+$"
      },
      {
       "expr": "active:${lx.factsheet.lifecycle.active}",
       "regexMatch": ".*:.+$"
      },
      {
       "expr": "phaseOut:${lx.factsheet.lifecycle.phaseOut}",
       "regexMatch": ".*:.+$"
      },
      {
       "expr": "phaseIn:${lx.factsheet.lifecycle.phaseIn}",
       "regexMatch": ".*:.+$"
      }
     ]
    }
   ]
  }
 ]
}
{
 "connectorType": "lxExport",
 "connectorId": "007",
 "connectorVersion": "1.0.0",
 "lxVersion": "1.0.0",
 "content": [
  {
   "type": "Application",
   "id": "28fe4aa2-6e46-41a1-a131-72afb3acf256",
   "data": {
    "lifecycleTimes": [
     "endOfLife:2021-08-06",
     "active:2018-08-06",
     "phaseOut:2021-05-06",
     "phaseIn:2018-05-06"
    ]
   }
  },
  {
   "type": "Application",
   "id": "001d3e51-a3be-4d5b-90f7-5b7a9e0b9458",
   "data": {
    "lifecycleTimes": [
     "active:2018-08-06",
     "phaseIn:2018-05-06"
    ]
   }
  }
 ]
}

🚧

Empty values are not written to the LDIF

Please note that the list in the result LDIF does not always contain all elements but only the ones that are populated. This is done by additional configuration of a "regexMatch" key eliminating all values with no content. By removing this, the second data object would contain all four elements including the empty ones with values like "phaseOut:"

LDIF from Processor Writing Relations: Map

The desired LDIF output and sample outbound processor are:

{
 "scope": {
  "facetFilters": [],
  "ids": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the outboundFactSheet processor",
   "enabled": true,
   "fields": [],
   "relations": {
    "filter": [
     "relApplicationToITComponent"
    ],
    "fields": [
     "description"
    ],
    "targetFields": [
     "displayName",
     "externalId"
    ]
   },
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "relations"
     },
     "mode": "list",
     "values": [
      {
       "forEach": {
        "elementOf": "${lx.relations}",
        "filter": "${true}"
       },
       "map": [
        {
         "key": "relationName",
         "value": "${integration.output.valueOfForEach.type}"
        },
        {
         "key": "object",
         "value": "${integration.output.valueOfForEach}"
        }
       ]
      }
     ]
    }
   ]
  }
 ]
}
{
 "connectorType": "lxExport",
 "connectorId": "007",
 "connectorVersion": "1.0.0",
 "lxVersion": "1.0.0",
 "content": [
  {
   "type": "Application",
   "id": "28fe4aa2-6e46-41a1-a131-72afb3acf256",
   "data": {
    "relations": [
     {
      "object": {
       "id": "5da3529f-2d40-4f00-9a22-ab8ddb4fc216",
       "type": "relApplicationToITComponent",
       "target": {
        "id": "20be4187-c384-4557-a6eb-c7a3d1e5eb40",
        "type": "ITComponent",
        "displayName": "Visual Studio 2011"
       },
       "description": ""
      },
      "relationName": "relApplicationToITComponent"
     },
     {
      "object": {
       "id": "51663856-ca7e-4a53-8412-14e6811df0ab",
       "type": "relApplicationToITComponent",
       "target": {
        "id": "5719d96b-4c45-497a-b006-c77e0d8e767b",
        "type": "ITComponent",
        "displayName": "meshlab IT Application Hosting"
       },
       "description": ""
      },
      "relationName": "relApplicationToITComponent"
     }
    ]
   }
  },
  {
   "type": "Application",
   "id": "001d3e51-a3be-4d5b-90f7-5b7a9e0b9458",
   "data": {
    "relations": [
     {
      "object": {
       "id": "509511e4-b5ff-43b1-b523-1ae5eb0522e0",
       "type": "relApplicationToITComponent",
       "target": {
        "id": "1d05f6ca-2d81-4998-8e6a-be70a0c33378",
        "type": "ITComponent",
        "displayName": "Test Cisco Router 3925"
       },
       "description": ""
      },
      "relationName": "relApplicationToITComponent"
     }
    ]
   }
  }
 ]
}

In this case, we again use the output mode "list" to generate a list and not only select the first valid result (We expect potentially multiple relations). Output of maps will work with mode "selectFirst" as well and export a single map as a value.

In the above example we use two new concepts:

Definitions: map & forEach

Data TypeDetails
mapHere we define the set of key names and value names we want to see in the output. Both are evaluated dynamically and can contain JUEL expressions referencing available data like Fact Sheet data.
forEach*Following the same pattern we see for the forEach in inbound processors, we iterate over a given list variable. In the example the list of found relations. For each relation entry we expect to see one map with the configured keys and values.

📘

Expression based filtering for Outbound Data Processors

The forEach as well provides an expression based filtering to e.g. only output specific relations (${integration.output.valueOfForEach.type=='relApplicationToITComponent'} in case multiple different relations have been requested in the relations section. More useful filtering may be done on target id or specific fields of a Fact Sheet or relation.

To learn more about forEach and other advanced functionality please see Advanced.

Use Relations to Create Separate Data Object

Last example shows how to use the relation (alternatively tag or subscription) information to create separate data object from it.

The example as well shows, how to filter specific relations from being written to the LDIF. In the example all relations with description "skipme" will not be exported. Please see the Setting Filters section for more information on filters.

Example outbound processor that uses relations to create a separate data object:

{
 "scope": {
  "facetFilters": [
   {
    "keys": [
     "Application"
    ],
    "facetKey": "FactSheetTypes",
    "operator": "OR"
   }
  ],
  "ids": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Relations relFromApp",
   "processorDescription": "Export all relations for Apps",
   "filter": {
    "advanced": "${integration.valueOfForEach.description!='skipme'}"
   },
   "enabled": true,
   "fields": [
    "description",
    "name"
   ],
   "relations": {
    "filter": [
     "relApplicationToBusinessCapability"
    ],
    "fields": [
     "description",
     "activeFrom",
     "activeUntil",
     "functionalSuitability"
    ],
    "targetFields": []
   },
   "forEach": "${lx.relations}",
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "0"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "lxRelApplicationToBusinessCapability"
      }
     ]
    },
    {
     "key": {
      "expr": "lxId"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "lxTargetId"
     },
     "values": [
      {
       "expr": "${integration.valueOfForEach.target.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "lxFsName"
     },
     "values": [
      {
       "expr": "${lx.factsheet.name}"
      }
     ]
    }
   ]
  }
 ]
}

Export Metrics

Integration API allows to export metrics information visible in a chart on a Fact Sheet to an LDIF file.
This is advanced configuration and requires deeper understanding of Metrics data and configuration in SAP LeanIX.
The Fact Sheet processor will contain one or more definitions to point to series of data points stored in the Metrics and displayed as a diagram attached to the Fact Sheet.
The configuration allows flexibility to customize the selection and aggregation of data points even different from the current display on the Fact Sheet as Integration API accesses the raw data not the processed data visible in the UI. Multiple data series as e.g. displayed on a stacked bar chart need to be configured as separate configurations in the processor and will be accessed separately when writing to the LDIF.
This example shows how to export Metrics data and needs to be adjusted to the defined metrics series in the workspace.
Multiple different configurations all need to define different values for the "name" key. This value is then used to access the collected data. Please use only characters that are supported by JUEL, avoid any special characters. In the example below "variableName" is used in the definition and then in the output section to access the data and write it to the LDIF.
"comparator" currently only supports "=" and intended for future extension.
"aggregationFunction" does support all standard influx aggregations supporting a column as parameter (see https://influxdbcom.readthedocs.io/en/latest/content/docs/v0.6/api/aggregate_functions/)

Example outboundFactsheet processor that exports metrics:

{
 "scope": {
  "ids": [
   "28fe4aa2-6e46-41a1-a131-72afb3acf256"
  ],
  "facetFilters": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the processor",
   "enabled": true,
   "fields": [
    "lifecycle",
    "location",
    "createdAt",
    "technicalSuitabilityDescription",
    "description"
   ],
   "relations": {
    "filter": [
     "relToParent",
     "relApplicationToITComponent"
    ],
    "fields": [
     "description"
    ],
    "targetFields": [
     "displayName",
     "externalId"
    ],
    "constrainingRelations": false
   },
   "tags": {
    "groups": [
     "Other tags",
     "Cloud Transformation"
    ]
   },
   "subscriptions": {
    "types": [
     "RESPONSIBLE"
    ]
   },
   "documents": {
    "filter": ".*"
   },
   "metrics": [
    {
     "name": "variableName",
     "measurement": "money",
     "fieldName": "dollars_per_day",
     "aggregationFunction": "MEAN",
     "groupBy": "1h",
     "start": "2020-01-20T00:00:00Z",
     "duration": "P0DT24H30M",
     "rules": {
      "key": "factSheetId",
      "comparator": "=",
      "compareWith": "${lx.factsheet.id}"
     }
    }
   ],
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "values"
     },
     "values": [
      {
       "expr": "${integration.toJson(lx.metrics.variableName.values)}"
      }
     ],
     "optional": true
    },
    {
     "key": {
      "expr": "fields"
     },
     "values": [
      {
       "expr": "${integration.toJson(lx.metrics.variableName.fields)}"
      }
     ],
     "optional": true
    },
    {
     "key": {
      "expr": "series"
     },
     "values": [
      {
       "expr": "${integration.toJson(lx.metrics.variableName.series)}"
      }
     ],
     "optional": true
    },
    {
     "key": {
      "expr": "metrics"
     },
     "mode": "list",
     "values": [
      {
       "forEach": {
        "elementOf": "${lx.metrics.variableName.series}",
        "filter": "${true}"
       },
       "map": [
        {
         "key": "time",
         "value": "${integration.output.valueOfForEach.time}"
        },
        {
         "key": "value",
         "value": "${integration.output.valueOfForEach.values}"
        }
       ]
      }
     ]
    }
   ]
  }
 ]
}

Filtering on items that have changed recently

Integration API can produce 'delta' extracts, reflecting only recently-updated Fact Sheets. This functionality is scoped to updates for entries in the audit history for specific Fact Sheets; It does not work for changes on documents or relations.

To filter, just add an additional key "updatedInDuration" to the filter section as shown below in the example:

Example processor that filters items by the change date:

{
 "scope": {
  "ids": [
   "28fe4aa2-6e46-41a1-a131-72afb3acf256"
  ],
  "facetFilters": []
 },
 "processors": [
  {
   "processorType": "outboundFactSheet",
   "processorName": "Export to LDIF",
   "processorDescription": "This is an example how to use the processor",
   "filter": {
    "updatedInDuration": "${header.customFields.age}"
   },
   "enabled": true,
   "fields": [
    "lifecycle",
    "location",
    "createdAt",
    "technicalSuitabilityDescription",
    "description"
   ],
   "relations": {
    "filter": [
     "relToParent",
     "relApplicationToITComponent"
    ],
    "fields": [
     "description"
    ],
    "targetFields": [
     "displayName",
     "externalId"
    ],
    "constrainingRelations": false
   },
   "tags": {
    "groups": [
     "Other tags",
     "Cloud Transformation"
    ]
   },
   "subscriptions": {
    "types": [
     "RESPONSIBLE"
    ]
   },
   "documents": {
    "filter": ".*"
   },
   "output": [
    {
     "key": {
      "expr": "content.id"
     },
     "values": [
      {
       "expr": "${lx.factsheet.id}"
      }
     ]
    },
    {
     "key": {
      "expr": "content.type"
     },
     "values": [
      {
       "expr": "${lx.factsheet.type}"
      }
     ]
    },
    {
     "key": {
      "expr": "updatedAt"
     },
     "mode": "selectFirst",
     "values": [
      {
       "expr": "${lx.factsheet.updatedAt}"
      }
     ]
    }
   ]
  }
 ]
}

To pass the value for updatedInDuration, just use the below LDIF when calling the outbound run.
The example exports all Fact Sheets that changed in the last 3 days. Of course it is possible to add the P3D directly into the configuration if this does not need to be a parameter.

📘

Duration Syntax

Duration is encoded in ISO 8601 Duration Format

Example LDIF that contains the duration for updates to be exported:

{
 "connectorType": "test",
 "connectorId": "test",
 "connectorVersion": "1.0.0",
 "processingDirection": "outbound",
 "processingMode": "partial",
 "customFields": {
  "age": "P3D"
 }
}

Translate list entries

A first use case covers the need to translate all entries of a list of values read from a Fact Sheet. Combining the option to iterate over a list of values in the output section and being able to configure this multiple times to collect results to output and on top of this filter by specific entries of the list.
We iterate over the list of entries multiple times. One time for each known option we want to translate. And we skip all other entries of the list. As a result, we collected a list of translated values.
The solution can be used for inbound and outbound processors both.

Example of translating all entries of a list to be exported:

{
 "key": {
  "expr": "myGermanValues"
 },
 "mode": "list",
 "values": [
  {
   "forEach": {
    "elementOf": "${myList}",
    "filter": "${integration.output.valueOfForEach==’english value 1’}"
   },
   "expr": "Deutscher Wert 1"
  },
  {
   "forEach": {
    "elementOf": "${myList}",
    "filter": "${integration.output.valueOfForEach==’english value 2’}"
   },
   "expr": "Deutscher Wert 2"
  }
 ]
}

How to export Fact Sheets that are archived

To include archived Fact Sheets when using outbound processors, a flag "omitArchivedFactSheets" needs to be set to false as in the sample below. Default for Integration API is to ignore all archived Fact Sheets when exporting.

Example scope definition to export Fact Sheets including archived ones:

{
  "scope": {
    "omitArchivedFactSheets": false,
    "ids": [],
    "facetFilters": [
      {
        "keys": [
          "BusinessCapability"
        ],
        "facetKey": "FactSheetTypes",
        "operator": "OR"
      },
      {
        "keys": [
          "archived"
        ],
        "facetKey": "TrashBin",
        "operator": "OR"
      }
    ]
  },
  "processors": [...]
}

How to export large amounts of data

To export large amounts of data, in case this is not possible to read data from the /synchronizationRuns/{id}/resultsand the response is too big the option which you have is to fetch data from blob storage.

{
  "scope": {},
  "processors": [],
  "dataConsumer": {
    "type": "leanixStorage"
  }
}

Fetch the resultsUrl by using the below and then use this to stream results from the Azure Blob storage.

Endpoint : https://<customerdomain>.leanix.net/services/integration-api/v1/synchronizationRuns/<id>/resultsUrl
Method : GET

The response can look like this below

{
  "url": "https://leanixsomething.blob.core.windows.net/xxxx"
}