Die Integration von Microsoft Dynamics 365 in Jira Data Center

Sie möchten Microsoft Dynamics und Ihr Atlassian Jira miteinander verbinden? Lesen Sie, wie uns die Integration gelungen ist.

Die Ablösung unseres bisherigen Customer-Relationship-Management (CRM) Systems durch Microsoft Dynamics war ein großes Unterfangen. Ein Projektteam aus Mitgliedern verschiedener Abteilungen stellte sicher, dass es einen möglichst reibungslosen Übergang für unsere Mitarbeiter gab.  Ein wichtiger Aspekt dabei war, dass unsere Kollegen weiterhin wie bisher gewohnt ihre Kundendaten direkt in den Jira-Tickets nutzen können, z.B. die Kontaktdaten eines Kunden oder wichtige vertriebliche Informationen zu Aufträgen oder Lizenzen. Da wir keine für unsere Zwecke passende Marketplace-App gefunden haben, entwickelten wir eine eigene Jira-App, welche die Kommunikation mit Dynamics übernimmt.

Im Folgenden möchte ich Ihnen aufzeigen, welche Voraussetzungen dafür erfüllt sein mussten, wie die technische Umsetzung erfolgte und welchen Herausforderungen wir uns dabei stellen mussten.

Bevor es losgehen kann

Damit unsere Jira-App die Microsoft Dynamics Services nutzen kann, ist eine Registrierung der Anwendung im Microsoft Azure Portal nötig.

Dort bekommt die Jira-Anwendung eindeutige IDs zugeordnet. Dadurch wird sichergestellt, dass Dynamics Anfragen von unserem Jira akzeptiert. Denn die Kommunikation zwischen den beiden Anwendungen läuft ausschließlich über die Common Data Service Web API von Dynamics, eine RESTful API. Da die Web API auf offenen Standards aufbaut, kann sie mit verschiedenen Programmiersprachen und Plattformen verwendet werden. In unserem Fall erfolgen die Anfragen an Dynamics durch eine JavaScript-Lösung.

Integration von Microsoft Dynamics 365 in Jira Data Center

Komponenten der Jira-App

Unsere Jira-App besteht aus zwei Teilen: einem Konfigurationsteil (umgesetzt mittels Atlassian SDK) und dem Kommunikationsteil (umgesetzt mittels JavaScript). 

Im Konfigurationsteil erzeugen wir eine Konfigurationsseite in der Jira-Administration und tragen dort die im vorherigen Abschnitt erwähnten eindeutigen Identifikatoren aus dem Azure Portal ein. Somit kann sich unsere App später bei Dynamics authentifizieren.

Integration von Microsoft Dynamics 365 in Jira Data Center

Der JavaScript-Part ist für die Kommunikation zwischen Jira und Dynamics zuständig und stellt die Verbindung zwischen beiden Welten her. Wir haben uns für eine client-seitige Umsetzung via JavaScript entschieden, um Berechtigungsprüfungen im Client zu ermöglichen und die Last auf dem Jira-Server so gering wie möglich zu halten. 

Authentifizierung bei Dynamics

Damit die Dynamics-Daten in Jira genutzt werden können, muss sich der User zunächst via Popup-Dialog in sein Microsoft-Online-Konto einloggen und authentifizieren. Dabei wird nur OAuth als Authentifizierung unterstützt. Als Antwort erhält Jira einen Token. Dieser wird nun bei jeder Anfrage an Dynamics im Response-Header mitgeschickt. Solange der Token gültig ist, erfolgt die Anmeldung bei Dynamics im Hintergrund, ohne dass der Nutzer sich erneut einloggen muss. Für das Authentifizierungshandling verwenden wir die Microsoft Authentication Library und im speziellen dessen JavaScript-Framework MSAL.js

Daten abfragen

Die Abfragen der Daten von Dynamics erfolgen mithilfe der oben bereits erwähnten RESTful Common Data Service Web API. Da es sich um eine recht komplexe API handelt, stellt Microsoft eine ausführliche Dokumentation mit vielen Beispielen zur Verfügung. Grundlage bilden sogenannte Entitäten, in unserem Fall z.B. account (Firmendaten) und contact (Personendaten). Um beispielsweise den Namen und die vollständige Adresse einer bestimmten Firma abzufragen, wird folgende GET-Anfrage an die Dynamics Web API geschickt: https://<dynamics-crm-url>/api/data/v9.1/accounts(8c48716a-05b9-e911-a827-000d3ab08ce9)?$select=name,address1_composite

Dabei stellt der alphanumerische Code 8c48716a-05b9-e911-a827-000d3ab08ce9 die eindeutige ID (accountid) des gewünschten Datensatzes in Dynamics dar. Mit $select= können die angeforderten Daten auf bestimmte Attribute eingeschränkt werden, in unserem Fall auf den Namen und die Adresse. Als Antwort erhält unser Jira die angeforderten Attribute im JSON-Format:
 

{
"@odata.context": "https://<dynamics-crm-url>/api/data/v9.1/$metadata#accounts(name,address1_composite)/$entity",
"@odata.etag": "W/\"5822819\"",
"name": "Communardo GmbH",
"address1_composite": "Kleiststr. 10a\r\n\r\n01129 Dresden\r\nDeutschland",
"address1_line1": "Kleiststr. 10a",
"accountid": "8c48716a-05b9-e911-a827-000d3ab08ce9",
"address1_city": "Dresden",
"address1_postalcode": "01129",
"address1_country": "Deutschland"
}

Anfragen bündeln

Durch diese Komplexität sind teilweise bis zu vier Abfragen an die Dynamics Web API nötig, um alle konfigurierten Attribute eines Firmen-Datensatzes abzufragen. Um die Anzahl an Anfragen zu verringern, bietet die Web API die Möglichkeit einer sogenannten Batch-Operation, welche wir auch in unserer App nutzen. Dabei werden mehrere Anfragen in einer einzigen HTTP-Anfrage gruppiert. Laut Dokumentation können Batch-Anfragen bis zu 1000 individuelle Anfragen enthalten. Einschränkungen dabei sind, dass die URLs für GET-Anfragen innerhalb der Batch-Anfrage auf 32768 Zeichen limitiert ist. Weiterhin darf eine Batch-Anfrage keine anderen Batch-Anfragen enthalten.

Batch-Anfragen werden als POST-Anfrage via https://<dynamics-crm-url>/api/data/v9.1/$batch an Dynamics gesendet. Die eigentlichen Anfragen werden im Request Body als Text mitgeschickt, wie im Folgenden anhand von vier GET-Anfragen zu sehen:

--1600928734395
Content-Type: application/http
Content-Transfer-Encoding:binary

GET <dynamics-crm-url>/api/data/v9.1/accounts(8c48716a-05b9-e911-a827-000d3ab08ce9) HTTP/1.1
Host: localhost:8060

--1600928734395
Content-Type: application/http
Content-Transfer-Encoding:binary

GET <dynamics-crm-url>/api/data/v9.1/EntityDefinitions(LogicalName='account') eq 'address1_composite' or LogicalName eq 'address1_fax' or LogicalName eq 'com_business_line' or LogicalName eq 'com_supportdatenblatt' or LogicalName eq 'creditlimit' or LogicalName eq 'new_sicherheitsstufe' or LogicalName eq 'numberofemployees' or LogicalName eq 'com_auftraege' or LogicalName eq 'com_supportvertraege' or LogicalName eq 'com_vertriebsbeauftragter' or LogicalName eq 'new_customerservicemanager' or LogicalName eq 'parentaccountid') HTTP/1.1
Host: localhost:8060

--1600928734395
Content-Type: application/http
Content-Transfer-Encoding:binary

GET <dynamics-crm-url>/api/data/v9.1/EntityDefinitions(LogicalName='account')/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata eq 'address1_composite' or LogicalName eq 'address1_fax' or LogicalName eq 'com_business_line' or LogicalName eq 'com_supportdatenblatt' or LogicalName eq 'creditlimit' or LogicalName eq 'new_sicherheitsstufe' or LogicalName eq 'numberofemployees' or LogicalName eq 'com_auftraege' or LogicalName eq 'com_supportvertraege' HTTP/1.1
Host: localhost:8060

--1600928734395
Content-Type: application/http
Content-Transfer-Encoding:binary

GET <dynamics-crm-url>/api/data/v9.1/accounts(8c48716a-05b9-e911-a827-000d3ab08ce9) HTTP/1.1
Host: localhost:8060

--1600928734395--

Als Antwort erhält man ebenfalls ein Text-Dokument, welches geparst und als JSON abgespeichert werden kann, z.B.:

--batchresponse_6f7cbc73-6da4-478e-85dc-11a3b5975f27
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1200OK
ETag: W/"18333368"
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0

{"@odata.context":"https://<dynamics-crm-url>/api/data/v9.1/$metadata#accounts(com_jirasearchfield,address1_composite,address1_fax,com_business_line,com_supportdatenblatt,creditlimit,new_sicherheitsstufe,numberofemployees,com_auftraege,com_supportvertraege)/$entity","@odata.etag":"W/\"18333368\"","com_jirasearchfield":"Communardo GmbH","address1_composite":"Kleiststr.\r\n\r\n01129 Dresden\r\nDeutschland","address1_fax":null,"com_business_line":181410000,[...]}

--batchresponse_6f7cbc73-6da4-478e-85dc-11a3b5975f27
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1200OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0

{"@odata.context":"https://<dynamics-crm-url>/api/data/v9.1/$metadata#EntityDefinitions(LogicalName,Attributes(LogicalName,AttributeType,DisplayName))/$entity","LogicalName":"account",
"MetadataId":"70816501-edb9-4740-a16c-6a5efbc05d84","Attributes":[{"@odata.type":"#Microsoft.Dynamics.CRM.PicklistAttributeMetadata","LogicalName":"com_business_line","AttributeType":"Picklist",
"MetadataId":"7af6128f-25e1-ea11-a813-000d3ad8b29c","DisplayName":{"LocalizedLabels":[{"Label":"Business Line (Supportvertrag)","LanguageCode":1031,"IsManaged":false,"MetadataId":"7ff6128f-25e1-ea11-a813-000d3ad8b29c","HasChanged":null}],"UserLocalizedLabel":{"Label":"Business Line (Supportvertrag)","LanguageCode":1031,"IsManaged":false,"MetadataId":"7ff6128f-25e1-ea11-a813-000d3ad8b29c","HasChanged":null}}},[...]]}

--batchresponse_6f7cbc73-6da4-478e-85dc-11a3b5975f27
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1200OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0

{"@odata.context":"https://<dynamics-crm-url>/api/data/v9.1/$metadata#EntityDefinitions('account')/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata(LogicalName,OptionSet(Options),GlobalOptionSet(Options))","value":[{"LogicalName":"com_business_line","MetadataId":"7af6128f-25e1-ea11-a813-000d3ad8b29c","OptionSet":{"MetadataId":"81f6128f-25e1-ea11-a813-000d3ad8b29c","Options":[{"Value":181410000,"Color":"#0000ff","IsManaged":false,"ExternalValue":"","ParentValues":[],"MetadataId":null,"HasChanged":null,"Label":{"LocalizedLabels":[{"Label":"Atlassian Solutions","LanguageCode":1031,"IsManaged":false,"MetadataId":"7bf6128f-25e1-ea11-a813-000d3ad8b29c","HasChanged":null}],[...]]}}]}

--batchresponse_6f7cbc73-6da4-478e-85dc-11a3b5975f27
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1200OK
ETag: W/"18333368"
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0

{"@odata.context":"https://<dynamics-crm-url>/api/data/v9.1/$metadata#accounts(com_vertriebsbeauftragter,new_CustomerServiceManager,parentaccountid,com_vertriebsbeauftragter(),new_CustomerServiceManager(),parentaccountid())/$entity",
"@odata.etag":"W/\"18333368\"","accountid":"8c48716a-05b9-e911-a827-000d3ab08ce9","com_vertriebsbeauftragter":{"@odata.etag":"W/\"17591002\"","ownerid":"ab719acb-297f-ea11-a813-000d3ab95ec9",
"address1_fax":"+49 (351) 833 82-299","organizationid":"d47b658d-6df6-43f6-9ef4-8bdac7d6b4a7","address1_postofficebox":null,"accessmode":0,"address1_upszone":null,"photourl":null,"address1_latitude":null,
"address1_shippingmethodcode":1,"address1_utcoffset":null,"_createdonbehalfby_value":null,"homephone":null,"skills":null,"emailrouteraccessapproval":1,"address2_latitude":null,"address1_longitude":null,"governmentid":null,"address2_longitude":null,"_createdby_value":null,"defaultfilterspopulated":false,"address1_telephone3":null,"mobilephone":null,"address2_fax":null,"preferredaddresscode":1,"address2_city":null,
"defaultodbfoldername":"Dynamics365","address2_stateorprovince":null,"address2_line2":null,"userpuid":"10033FFF907B6BDF","firstname":"Bob",[...]}}

--batchresponse_6f7cbc73-6da4-478e-85dc-11a3b5975f27--

Weitere Informationen und Beispiele zu Batch-Anfragen erhalten Sie hier: https://docs.microsoft.com/de-de/powerapps/developer/data-platform/webapi/execute-batch-operations-using-web-api

Berechtigungsprüfungen

Was die Datensicherheit betrifft stellt Dynamics selbst sicher, dass jeder nur die Datensätze sehen darf, für die er berechtigt ist. Fragt ein Nutzer über die Web API einen Datensatz an, den er nicht sehen darf, wird eine Fehlermeldung als Antwort zurückgesendet, die dem Nutzer als entsprechende Meldung im Benutzerdefinierten Feld angezeigt wird.

Fazit

Unsere client-seitige Lösung ermöglicht es, mittels der komplexen Web API von Dynamics, beliebige Daten aus einem Dynamics CRM im Jira abzurufen und für Nutzer lesbar darzustellen. Bei der Entwicklung der Lösung waren das Authentifizierunghandling und die komplexen Abfragen an die Web API, um alle gewünschten Daten in Jira anzuzeigen, die größten Herausforderungen. Weiterhin geht diese Komplexität mit einer teilweise hohen Anzahl an REST-Anfragen einher, die aber durch das Bündeln in Batch-Operationen reduziert werden können.

Unsere Jira-Dynamics-Integration-App ist nun seit mehreren Monaten erfolgreich in unserem Communardo-Jira im Einsatz und unterstützt unsere Mitarbeiter aus Vertrieb und Support bei ihrer täglichen Arbeit. 

Da es sich, abgesehen vom Konfigurationsteil, um eine reine JavaScript-Lösung handelt, ist eine künftige Integration in Jira Cloud mit wenigen Anpassungen möglich

Sie nutzen Microsoft Dynamics 365 und wollen es in Ihr Jira Data Center integrieren?

Profitieren Sie von unseren Erfahrungen. Wir beraten Sie gern umfassend zu Ihren Möglichkeiten.

Frenzel Luise
28. Juni 2021 

Sie haben Fragen oder möchten sich von uns beraten lassen?

Gerne stehen wir für Ihre Fragen zur Verfügung. Nutzen Sie einfach unser Kontaktformular.