Announcing the Saviynt Knowledge Exchange unifying the Saviynt forums, documentation, training,
and more in a single search tool across platforms. Read the announcement here.

Issue Importing Roles from Coupa into Saviynt

markmora
New Contributor III
New Contributor III

Hi everyone,

I'm encountering a challenge while importing roles from Coupa into Saviynt. There isn't a dedicated endpoint for roles in Coupa, and the user endpoint bundles roles into an array. When I test the call with Postman, it correctly returns multiple role objects within a "roles" array.

However, when I use the same call in Saviynt's ImportAccountEntJSON, it imports the array as a single nested role, instead of two separate roles. Below is the relevant part of my JSON configuration and the response I get from Postman:

ImportAccountEntJSON
{
"globalSettings": {
"dateFormat": "yyyy-MM-dd'T'HH:mm:ss'Z'"
},
"accountParams": {
"connection": "acctAuth",
"processingType": "SequentialAndIterative",
"call": {
"call1": {
"callOrder": 0,
"listField": "",
"keyField": "accountID",
"http": {
"url": "https://xxxx.coupahost.com/api/users?fields=%5B%22id%22%2C%22email%22%2C%22login%22%2C%22active%22%2...",
"httpMethod": "GET",
"httpHeaders": {
"Authorization": "${access_token}",
"Accept": "application/json"
}
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
},
"colsToPropsMap": {
"accountID": "id~#~char",
"name": "login~#~char",
"status": "active~#~char",
"customproperty5": "active~#~char",
"customproperty1": "email~#~char",
"customproperty2": "firstname~#~char",
"customproperty3": "lastname~#~char",
"customproperty4": "mention-name~#~char",
"customproperty7": "active~#~char",
"customproperty14": "created-at~#~char",
"customproperty15": "sso-identifier~#~char",
"customproperty16": "employee-number~#~char",
"customproperty31": "STORE#ACC#ENT#MAPPINGINFO~#~char"
}
}
}
},
"entitlementParams": {
"processingType": "SequentialAndIterative",
"entTypes": {
"Role": {
"entTypeOrder": 0,
"entTypeLabels": {},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 0,
"http": {
"httpHeaders": {
"Authorization": "${access_token}",
"Accept": "application/json"
},
"url": "https://xxxx.coupahost.com/api/users?id=2&fields=%5B%7B%22roles%22%3A%5B%22id%22%2C%22name%22%2C%22d...",
"httpContentType": "application/json",
"httpMethod": "GET",
"successResponses": {
"statusCode": [200]
}
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 100
}
},
"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "roles.id~#~char",
"entitlement_value": "roles.name~#~char",
"description": "roles.description~#~char"
}
}
}
}
}
},
"acctEntParams": {}
}

Postman Response:

[
{
"roles": [
{
"id": 3,
"name": "User",
"description": "Standard role for all users who need to create and/or approve requisitions"
},
{
"id": 10000,
"name": "Read Only",
"description": "Custom Role that adds read only ability to view transactional data. Please note, this role requires on-going maintenance when new features are added."
}
]
}
]

markmora_0-1706652844635.png

I attempted the following configuration in my ImportAccountEntJSON, but it only imports the first role without nesting and does not recognize the subsequent roles as separate entries:

"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "roles[0].id~#~char",
"entitlement_value": "roles[0].name~#~char",
"description": "roles[0].description~#~char"
}

Has anyone successfully mapped the array of roles objects to entitlements in Saviynt? Could you please share your approach or any suggestions you might have?

Much appreciated!

 

12 REPLIES 12

Shreya-B
New Contributor
New Contributor

Hi Mark,

We had a similar requirement where only our acc to ent mapping was nested, and the work around we found was to update nested roles into the syntax of the customproperty31 (used for accounttoentitlement mapping).

So instead of using:

"customproperty31": "STORE#ACC#ENT#MAPPINGINFO~#~char"

Try with:

"customproperty31": "#CONST#${String output1=response.roles.replaceAll(', ','\",\"'); beg= ' {\"Group\":{\"entIds\":[\"'; end= '\"],\"keyField\":\"entitlementID\"}}' ; output2= beg.concat(output1) ; finoutput= output2.concat(end) ; return finoutput}~#~char".

The idea is to bring it to this format which Saviynt would understand (Please replace the bold words as per your configuration)

ShreyaB_0-1706666771820.png

Hope this helps.

 

markmora
New Contributor III
New Contributor III

Many thanks for the the information; as you can see in the attached image, I now have the roles in the account customproperty31. However, how can I import them as entitlements instead of just a portion of the account cp31?

This is our ImportAccountEntJSON:

{
"accountParams": {
"connection": "acctAuth",
"processingType": "SequentialAndIterative",
"statusAndThresholdConfig": {
"accountsNotInImportAction": "Suspend",
"accountThresholdValue": 100
},
"call": {
"call1": {
"callOrder": 0,
"stageNumber": 0,
"http": {
"url": "https://xxx.coupahost.com/api/users?fields=%5B%22id%22%2C%22email%22%2C%22login%22%2C%22active%22%2C...",
"httpContentType": "application/json",
"httpMethod": "GET",
"httpHeaders": {
"Authorization": "${access_token}",
"Accept": "application/json"
}
},
"listField": "",
"keyField": "accountID",
"statusConfig": {
"active": "true",
"inactive": "false"
},
"colsToPropsMap": {
"accountID": "id~#~char",
"name": "login~#~char",
"status": "active~#~char",
"customproperty5": "active~#~char",
"customproperty1": "email~#~char",
"customproperty2": "firstname~#~char",
"customproperty3": "lastname~#~char",
"customproperty4": "mention-name~#~char",
"customproperty7": "active~#~char",
"customproperty14": "created-at~#~char",
"customproperty15": "sso-identifier~#~char",
"customproperty16": "employee-number~#~char",
"customproperty31": "#CONST#${String roles = response.roles.collect{ '\"' + it.id+ '\"' }.join(', '); return '{\"Role\":{\"entIds\":[' + roles + '],\"keyField\":\"entitlementID\"}}';}~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
},
"acctEntMappings": {
"Role": {
"importAsEntitlement": true,
"listPath": "roles",
"idPath": "id",
"keyField": "entitlementID"
}
}
},
"entitlementParams": {
"processingType": "SequentialAndIterative",
"entTypes": {
"Role": {
"entTypeOrder": 0,
"entTypeLabels": {},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 0,
"http": {
"httpHeaders": {
"Authorization": "${access_token}",
"Accept": "application/json"
},
"url": "https://xxx.coupahost.com/api/users?id=2&fields=%5B%7B%22roles%22%3A%5B%22id%22%2C%22name%22%2C%22de...",
"httpContentType": "application/json",
"httpMethod": "GET",
"successResponses": {
"statusCode": [200]
}
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 100
}
},
"listField": "roles",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"
}
}
}
}
}
},
"acctEntParams": {
"processingType": "acctToEntMapping"
}
}


markmora_0-1706725883279.png

markmora_2-1706725973331.png



{
"accountParams": {
"connection": "acctAuth",
"processingType": "SequentialAndIterative",
"statusAndThresholdConfig": {
"accountsNotInImportAction": "Suspend",
"accountThresholdValue": 100
},
"call": {
"call1": {
"callOrder": 0,
"stageNumber": 0,
"http": {
"url": "https://rushi.coupahost.com/api/users",
"httpContentType": "application/json",
"httpMethod": "GET",
"httpHeaders": {
"X-COUPA-API-KEY": "rushi",
"Accept": "application/json"
}
},
"listField": "",
"keyField": "accountID",
"statusConfig": {
"active": "true",
"inactive": "false"
},
"colsToPropsMap": {
"accountID": "id~#~char",
"customproperty1": "email~#~char",
"name": "login~#~char",
"status": "active~#~char",
"customproperty2": "firstname~#~char",
"customproperty3": "lastname~#~char",
"customproperty4": "fullname~#~char",
"customproperty5": "call1.message.mention-name~#~char",
"customproperty7": "custom-fields.cost-center~#~char",
"customproperty8": "purchasing-user~#~char",
"customproperty9": "expense-user~#~char",
"customproperty10": "sourcing-user~#~char",
"customproperty11": "inventory-user~#~char",
"customproperty12": "contracts-user~#~char",
"customproperty13": "analytics-user~#~char",
"customproperty14": "created-by.id~#~char",
"customproperty15": "sso-identifier~#~char",
"customproperty16": "employee-number~#~char",
"customproperty17": "authentication-method~#~char",
"customproperty23": "default-currency.code~#~char",
"customproperty24": "requisition-approval-limit.name~#~char",
"customproperty25": "invoice-approval-limit.name~#~char",
"customproperty26": "manager.login~#~char",
"customproperty31": "STORE#ACC#ENT#MAPPINGINFO~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
},
"acctEntMappings": {
"Role": {
"importAsEntitlement": false,
"listPath": "roles",
"idPath": "id",
"keyField": "entitlementID"
},
"User Group": {
"importAsEntitlement": false,
"listPath": "user-groups",
"idPath": "id",
"keyField": "entitlementID"
},
"Business Group": {
"importAsEntitlement": false,
"listPath": "content-groups",
"idPath": "id",
"keyField": "entitlementID"
},
"Account Group": {
"importAsEntitlement": false,
"listPath": "account_groups",
"idPath": "id",
"keyField": "entitlementID"
}
}
},
"entitlementParams": {
"processingType": "SequentialAndIterative",
"entTypes": {
"Role": {
"entTypeOrder": 0,
"entTypeLabels": {
"customproperty1": "Meta Created"
},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 1,
"http": {
"httpHeaders": {
"X-COUPA-API-KEY": "rushi",
"Accept": "application/json"
},
"url": "https://rushi.coupahost.com/api/roles",
"httpContentType": "application/json",
"httpMethod": "GET"
},
"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
}
},
"User Group": {
"entTypeOrder": 1,
"entTypeLabels": {
"customproperty1": "Meta Created"
},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 2,
"http": {
"httpHeaders": {
"X-COUPA-API-KEY": "rushi",
"Accept": "application/json"
},
"url": "https://rushi.coupahost.com/api/user_groups",
"httpContentType": "application/json",
"httpMethod": "GET"
},
"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
}
},
"Business Group": {
"entTypeOrder": 2,
"entTypeLabels": {
"customproperty1": "Meta Created"
},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 3,
"http": {
"httpHeaders": {
"X-COUPA-API-KEY": "rushi",
"Accept": "application/json"
},
"url": "https://rushi.coupahost.com/api/business_groups",
"httpContentType": "application/json",
"httpMethod": "GET"
},
"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
}
},
"Account Group": {
"entTypeOrder": 2,
"entTypeLabels": {
"customproperty1": "Meta Created"
},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 4,
"http": {
"httpHeaders": {
"X-COUPA-API-KEY": "rushi",
"Accept": "application/json"
},
"url": "https://rushi.coupahost.com/api/account_groups",
"httpContentType": "application/json",
"httpMethod": "GET"
},
"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
}
}
}
},
"acctEntParams": {
"entTypes": {
"Role": {
"call": {
"call1": {
"processingType": "acctToEntMapping"
}
}
}
}
}
}


Regards,
Rushikesh Vartak
If you find the response useful, kindly consider selecting Accept As Solution and clicking on the kudos button.

That is a generic json, coupa does not have a roles endpoint so it does not work, what I need to know is how to import entitlements if I already have them mapped in the account customproperty31 

markmora
New Contributor III
New Contributor III

@Shreya-B Any idea?

Your entitlementParams not mapping entitlement after access import ? what is error in logs


Regards,
Rushikesh Vartak
If you find the response useful, kindly consider selecting Accept As Solution and clicking on the kudos button.

The entitlement params from the previous json is being used, when access is imported no error is received, the job ends successfully but nothing is imported, could you check if the entitlementparams is correct?

Share API response


Regards,
Rushikesh Vartak
If you find the response useful, kindly consider selecting Accept As Solution and clicking on the kudos button.

Attached the api response from postman.

 

[
{
"roles": [
{
"id": 3,
"name": "User",
"description": "Standard role for all users who need to create and/or approve requisitions"
},
{
"id": 10000,
"name": "Read Only",
"description": "Custom Role that adds read only ability to view transactional data. Please note, this role requires on-going maintenance when new features are added."
}
]
}
]

"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"


Regards,
Rushikesh Vartak
If you find the response useful, kindly consider selecting Accept As Solution and clicking on the kudos button.

I have used the above but it doesn't seem to work, the response from postman includes a role root which I don't see that we are using it in the entitlementParams(), I attach the json I am using but it doesn't import anything and the logs, the endpoint is called cp13.

@rushikeshvartak @Shreya-B Any idea how to import the roles correctly?

markmora_0-1706767955347.png

{
"accountParams": {
"connection": "acctAuth",
"processingType": "SequentialAndIterative",
"statusAndThresholdConfig": {
"accountsNotInImportAction": "Suspend",
"accountThresholdValue": 100
},
"call": {
"call1": {
"callOrder": 0,
"stageNumber": 0,
"http": {
"url": "https://xxx.coupahost.com/api/users?fields=%5B%22id%22%2C%22email%22%2C%22login%22%2C%22active%22%2C...",
"httpContentType": "application/json",
"httpMethod": "GET",
"httpHeaders": {
"Authorization": "${access_token}",
"Accept": "application/json"
}
},
"listField": "",
"keyField": "accountID",
"statusConfig": {
"active": "true",
"inactive": "false"
},
"colsToPropsMap": {
"accountID": "id~#~char",
"name": "login~#~char",
"status": "active~#~char",
"customproperty5": "active~#~char",
"customproperty1": "email~#~char",
"customproperty2": "firstname~#~char",
"customproperty3": "lastname~#~char",
"customproperty4": "mention-name~#~char",
"customproperty7": "active~#~char",
"customproperty14": "created-at~#~char",
"customproperty15": "sso-identifier~#~char",
"customproperty16": "employee-number~#~char",
"customproperty31": "#CONST#${String roles = response.roles.collect{ '\"' + it.id+ '\"' }.join(', '); return '{\"Role\":{\"entIds\":[' + roles + '],\"keyField\":\"entitlementID\"}}';}~#~char"
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 10000
}
}
}
},
"acctEntMappings": {
"Role": {
"importAsEntitlement": true,
"listPath": "roles",
"idPath": "id",
"keyField": "entitlementID"
}
}
},
"entitlementParams": {
"processingType": "SequentialAndIterative",
"entTypes": {
"Role": {
"entTypeOrder": 0,
"entTypeLabels": {},
"call": {
"call1": {
"connection": "acctAuth",
"callOrder": 0,
"stageNumber": 0,
"http": {
"httpHeaders": {
"Authorization": "${access_token}",
"Accept": "application/json"
},
"url": "https://xxx.coupahost.com/api/users?id=2&fields=%5B%7B%22roles%22%3A%5B%22id%22%2C%22name%22%2C%22de...",
"httpContentType": "application/json",
"httpMethod": "GET",
"successResponses": {
"statusCode": [200]
}
},
"pagination": {
"offset": {
"offsetParam": "offset",
"batchParam": "limit",
"batchSize": 50,
"totalCountPath": 100
}
},
"listField": "",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"
}
}
}
}
}
},
"acctEntParams": {
"processingType": "acctToEntMapping"
}
}

Shreya-B
New Contributor
New Contributor

@markmora 

You could try to use the below in the entitlementParams()

"listField": "roles",
"keyField": "entitlementID",
"colsToPropsMap": {
"entitlementID": "id~#~char",
"entitlement_value": "name~#~char",
"description": "description~#~char"