Skip to content

Commit 8ac6b9f

Browse files
authored
Merge pull request #4 from JimDaly/main-jdaly-RetrieveOptions
Adding the RetrieveOptions action
2 parents 14e695a + 7d7e9cf commit 8ac6b9f

16 files changed

+294
-6
lines changed

Plugins/DataversePowerAutomateHelpers/AddToQueue.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ public class AddToQueue : IPlugin
1212
{
1313
/*
1414
Input Parameters:
15-
| String | SourceQueueName | Optional |
16-
| String | TargetEntityLogicalName | Required |
17-
| Guid | TargetId | Required |
18-
| String | DestinationQueueName | Required |
15+
| String | SourceQueueName | Optional | The name of the queue that the item should be moved from. |
16+
| String | TargetEntityLogicalName | Required | The logical name of the entity that represents the item to add to the queue. |
17+
| Guid | TargetId | Required | The Id of the item to add to the queue. |
18+
| String | DestinationQueueName | Required | The name of the queue to add the item to. |
1919
2020
Output Parameters:
2121
| Guid | QueueItemId |

Plugins/DataversePowerAutomateHelpers/DataversePowerAutomateHelpers.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<Compile Include="AddToQueue.cs" />
6565
<Compile Include="AddUserToRecordTeam.cs" />
6666
<Compile Include="Properties\AssemblyInfo.cs" />
67+
<Compile Include="RetrieveOptions.cs" />
6768
<Compile Include="Utility.cs" />
6869
</ItemGroup>
6970
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using Microsoft.Xrm.Sdk;
2+
using Microsoft.Xrm.Sdk.Messages;
3+
using Microsoft.Xrm.Sdk.Metadata;
4+
using System;
5+
using System.Linq;
6+
using System.ServiceModel;
7+
8+
namespace DataversePowerAutomateHelpers
9+
{
10+
public class RetrieveOptions : IPlugin
11+
{
12+
/*
13+
Input Parameters:
14+
| String | EntityLogicalName | Required | The LogicalName of the entity (or table) that contains the attribute (or column). |
15+
| String | LogicalName | Required | The LogicalName of the attribute (or column) that contains the options. |
16+
17+
18+
Output Parameters:
19+
| EntityCollection | Options | A List of 'expando' entities where properties represent option properties|
20+
21+
'Expando entities' are entities with no LogicalName set. Expando entities do not need to map to any entity metadata.
22+
23+
When returned using Web API they have this @odata.type value:
24+
25+
{
26+
"@odata.type": "#Microsoft.Dynamics.CRM.expando",
27+
"value": 1,
28+
"label": "Preferred Customer"
29+
}
30+
31+
*/
32+
33+
34+
public void Execute(IServiceProvider serviceProvider)
35+
{
36+
// Obtain the tracing service
37+
var tracingService =
38+
(ITracingService)serviceProvider.GetService(typeof(ITracingService));
39+
40+
// Obtain the execution context from the service provider.
41+
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
42+
43+
// Obtain the organization service reference which you will need for web service calls.
44+
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
45+
var service = serviceFactory.CreateOrganizationService(context.UserId);
46+
47+
try
48+
{
49+
50+
//Assign variables from context
51+
string entityLogicalName = (string)context.InputParameters["EntityLogicalName"];
52+
string logicalName = (string)context.InputParameters["LogicalName"];
53+
54+
55+
//Compose the request
56+
var req = new RetrieveAttributeRequest
57+
{
58+
EntityLogicalName = entityLogicalName,
59+
LogicalName = logicalName
60+
};
61+
62+
tracingService.Trace($"Sending RetrieveAttributeRequest using values EntityLogicalName:{entityLogicalName}, LogicalName:{ logicalName}");
63+
//Send the request
64+
var response = (RetrieveAttributeResponse)service.Execute(req);
65+
tracingService.Trace("RetrieveAttributeRequest call succeeded.");
66+
67+
//Define the EntityCollection to return
68+
var options = new EntityCollection();
69+
70+
//Add expando entities to the collection based on the type of attribute.
71+
switch (response.AttributeMetadata)
72+
{
73+
case BooleanAttributeMetadata b:
74+
75+
//True Option
76+
options.Entities.Add(new Entity()
77+
{
78+
Attributes = {
79+
{ "value", b.OptionSet.TrueOption.Value},
80+
{ "label", b.OptionSet.TrueOption.Label.UserLocalizedLabel.Label}
81+
}
82+
});
83+
84+
//False Option
85+
options.Entities.Add(new Entity()
86+
{
87+
Attributes = {
88+
{ "value", b.OptionSet.FalseOption.Value},
89+
{ "label", b.OptionSet.FalseOption.Label.UserLocalizedLabel.Label}
90+
}
91+
});
92+
93+
break;
94+
case MultiSelectPicklistAttributeMetadata m:
95+
m.OptionSet.Options.ToList().ForEach(o =>
96+
{
97+
var option = new Entity();
98+
option["value"] = o.Value;
99+
option["label"] = o.Label.UserLocalizedLabel.Label;
100+
options.Entities.Add(option);
101+
});
102+
break;
103+
case PicklistAttributeMetadata p:
104+
p.OptionSet.Options.ToList().ForEach(o =>
105+
{
106+
var option = new Entity();
107+
option["value"] = o.Value;
108+
option["label"] = o.Label.UserLocalizedLabel.Label;
109+
options.Entities.Add(option);
110+
});
111+
break;
112+
case StateAttributeMetadata se:
113+
se.OptionSet.Options.ToList().ForEach(o =>
114+
{
115+
var option = new Entity();
116+
option["value"] = o.Value;
117+
option["label"] = o.Label.UserLocalizedLabel.Label;
118+
//Special properties that only this type of attribute has
119+
option["defaultstatus"] = ((StateOptionMetadata)o).DefaultStatus;
120+
option["invariantname"] = ((StateOptionMetadata)o).InvariantName;
121+
options.Entities.Add(option);
122+
});
123+
break;
124+
case StatusAttributeMetadata su:
125+
su.OptionSet.Options.ToList().ForEach(o =>
126+
{
127+
var option = new Entity();
128+
option["value"] = o.Value;
129+
option["label"] = o.Label.UserLocalizedLabel.Label;
130+
//Special property that only this type of attribute has
131+
option["state"] = ((StatusOptionMetadata)o).State;
132+
options.Entities.Add(option);
133+
});
134+
break;
135+
default:
136+
throw new InvalidPluginExecutionException($"The {logicalName} attribute doesn't have options.");
137+
138+
}
139+
140+
context.OutputParameters["Options"] = options;
141+
142+
tracingService.Trace("sample_RetrieveOptions completed.");
143+
}
144+
catch (FaultException<OrganizationServiceFault> ex)
145+
{
146+
throw new InvalidPluginExecutionException($"An error occurred in sample_RetrieveOptions: {ex.Message}", ex);
147+
}
148+
149+
catch (Exception ex)
150+
{
151+
tracingService.Trace("sample_RetrieveOptions: {0}", ex.ToString());
152+
throw;
153+
}
154+
}
155+
}
156+
}

README.md

+89-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Below is a list of the available APIs:
1212

1313
- [sample_AddToQueue](#sample_addtoqueue)
1414
- [sample_AddUserToRecordTeam](#sample_AddUserToRecordTeam)
15+
- [sample_RetrieveOptions](#sample_RetrieveOptions)
1516

1617
If there are other Actions you want to use but are not able to, please open an issue for this GitHub repo.
1718

@@ -66,4 +67,91 @@ There are a lot of unneeded fields about the team template, when all that is nee
6667

6768
### After
6869

69-
![sample_AddUserToRecordTeam](media/sample_AddUserToRecordTeam.png)
70+
![sample_AddUserToRecordTeam](media/sample_AddUserToRecordTeam.png)
71+
72+
## sample_RetrieveOptions
73+
74+
Unfortunately for the moment, this Custom API serves as a repro for a bug in the Common Data Service (Current Environment) connector. When that bug is fixed, this Custom API should work in Power Automate as expected.
75+
76+
This Custom API addresses the need that many people have expressed to be able to retrieve the valid options for a given entity attribute. The Web API doesn't expose the equivalent to the [RetrieveOptionSet message](https://docs.microsoft.com/en-us/dotnet/api/microsoft.xrm.sdk.messages.retrieveoptionsetrequest?view=dynamics-general-ce-9) found in the SDK. But even this message is limited in capability because it only returns information about Global optionsets. There are many 'local' optionsets which are not defined globally.
77+
78+
Retrieving information about local optionsets in the Web API is made more complicated because the `OptionSet` property isn't part of the base `AttributeMetadata` class. It is only found in specific classes derived from `AttributeMetadata`. This requires that you know the sub-type before you send your request and cast the attribute in the URL, and there are five different types of attributes with options.
79+
80+
For example, if you want to get the options for an ordinary [PicklistAttributeMetadata](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/picklistattributemetadata) attribute, you need to compose a URL like this:
81+
82+
`GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes(LogicalName='accountcategorycode')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata/OptionSet?$select=Options`
83+
84+
However, the following classes also support OptionSets:
85+
86+
- [BooleanAttributeMetadata](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/booleanattributemetadata)
87+
- [MultiSelectPicklistAttributeMetadata](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/multiselectpicklistattributemetadata)
88+
- [StateAttributeMetadata](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/stateattributemetadata)
89+
- [StatusAttributeMetadata](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/statusattributemetadata)
90+
91+
More information [Query metadata using the Web API > Retrieving attributes](https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/webapi/query-metadata-web-api?view=dynamics-general-ce-9#retrieving-attributes)
92+
93+
What a Power Automate user expects is a simple way to retrieve the valid options without a lot of complexity. This Custom API provides this by requiring just two string values:
94+
95+
|Name|Type|Description|Is Required
96+
|--|--|--|--|
97+
|EntityLogicalName|String|The LogicalName of the entity that contains the attribute.|Yes|
98+
|LogicalName|String|The LogicalName of the attribute that contains the options. |Yes|
99+
100+
![RetrieveOptions](media/sample_RetrieveOptions.png)
101+
102+
Unfortunately, if you try to use the `sample_RetrieveOptions` action at the time this was written, you will get an error like the following:
103+
104+
![RetrieveOptionsError](media/sample_RetrieveOptionsError.png)
105+
106+
The error suggests the root of the problem:
107+
108+
`The API 'commondataserviceforapps' returned an invalid response for workflow operation 'Perform_an_unbound_action' of type 'OpenApiConnection'. Error details: 'The API operation 'PerformUnboundAction' requires the property 'body' to be of type 'Array' but is of type 'Object'.'`
109+
110+
This Custom API uses an `EntityCollection` return type. The Web API definition for the action looks like this:
111+
112+
```xml
113+
<Action Name="sample_RetrieveOptions">
114+
<Parameter Name="EntityLogicalName" Type="Edm.String" Nullable="false" Unicode="false" />
115+
<Parameter Name="LogicalName" Type="Edm.String" Nullable="false" Unicode="false" />
116+
<ReturnType Type="Collection(mscrm.crmbaseentity)" Nullable="false" />
117+
</Action>
118+
```
119+
120+
If you call the Action from PostMan, it works as expected:
121+
122+
**Request**
123+
124+
```http
125+
GET {{webapiurl}}sample_RetrieveOptions
126+
127+
{
128+
"EntityLogicalName" : "account",
129+
"LogicalName":"accountcategorycode"
130+
}
131+
```
132+
133+
134+
**Response Body**
135+
136+
```json
137+
{
138+
"@odata.context": "{{webapiurl}}$metadata#expando",
139+
"value": [
140+
{
141+
"@odata.type": "#Microsoft.Dynamics.CRM.expando",
142+
"value": 1,
143+
"label": "Preferred Customer"
144+
},
145+
{
146+
"@odata.type": "#Microsoft.Dynamics.CRM.expando",
147+
"value": 2,
148+
"label": "Standard"
149+
}
150+
]
151+
}
152+
```
153+
154+
An `EntityCollection` is an object rather than an Array. It returns an object with a `value` property just like an entity collection returned with this request: `GET {{webapiurl}}accounts`.
155+
156+
Hopefully this bug will be fixed soon and we will be able to use Custom API actions which return EntityCollection in Power Automate.
157+
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<customapi uniquename="sample_RetrieveOptions">
2+
<allowedcustomprocessingsteptype>0</allowedcustomprocessingsteptype>
3+
<bindingtype>0</bindingtype>
4+
<description default="Retrieves the options for a specific attribute that has them">
5+
<label description="Retrieves the options for a specific attribute that has them" languagecode="1033" />
6+
</description>
7+
<displayname default="Retrieve Options">
8+
<label description="Retrieve Options" languagecode="1033" />
9+
</displayname>
10+
<iscustomizable>1</iscustomizable>
11+
<isfunction>0</isfunction>
12+
<name>RetrieveOptions</name>
13+
<plugintypeid>
14+
<plugintypeid>7fbc50a6-6be4-4ae0-ba5d-e907b8c85a23</plugintypeid>
15+
</plugintypeid>
16+
</customapi>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<customapirequestparameter uniquename="EntityLogicalName">
2+
<description default="The logical name of the entity that contains the attribute with the options you want.">
3+
<label description="The logical name of the entity that contains the attribute with the options you want." languagecode="1033" />
4+
</description>
5+
<displayname default="Entity Logical Name">
6+
<label description="Entity Logical Name" languagecode="1033" />
7+
</displayname>
8+
<iscustomizable>1</iscustomizable>
9+
<isoptional>0</isoptional>
10+
<name>RetrieveOptions.EntityLogicalName</name>
11+
<type>10</type>
12+
</customapirequestparameter>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<customapirequestparameter uniquename="LogicalName">
2+
<description default="The logical name of the attribute that contains the options you want.">
3+
<label description="The logical name of the attribute that contains the options you want." languagecode="1033" />
4+
</description>
5+
<displayname default="Logical Name">
6+
<label description="Logical Name" languagecode="1033" />
7+
</displayname>
8+
<iscustomizable>1</iscustomizable>
9+
<isoptional>0</isoptional>
10+
<name>RetrieveOptions.LogicalName</name>
11+
<type>10</type>
12+
</customapirequestparameter>

Solutions/UnManaged/extracted/customizations.xml

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<PluginType AssemblyQualifiedName="DataversePowerAutomateHelpers.AddUserToRecordTeam, DataversePowerAutomateHelpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=47245e9890212fb6" PluginTypeId="904579e9-ac4e-46a1-8da9-950f05e5f883" Name="DataversePowerAutomateHelpers.AddUserToRecordTeam">
2323
<FriendlyName>3b53ce5e-42b4-4c6e-b89c-3e85bba6b26a</FriendlyName>
2424
</PluginType>
25+
<PluginType AssemblyQualifiedName="DataversePowerAutomateHelpers.RetrieveOptions, DataversePowerAutomateHelpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=47245e9890212fb6" PluginTypeId="7fbc50a6-6be4-4ae0-ba5d-e907b8c85a23" Name="DataversePowerAutomateHelpers.RetrieveOptions">
26+
<FriendlyName>ac740648-541a-4237-8392-b24338bfb794</FriendlyName>
27+
</PluginType>
2528
</PluginTypes>
2629
</PluginAssembly>
2730
</SolutionPluginAssemblies>

Solutions/UnManaged/extracted/solution.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LocalizedName description="Dataverse PowerAutomate Helpers" languagecode="1033" />
66
</LocalizedNames>
77
<Descriptions />
8-
<Version>1.0.0.1</Version>
8+
<Version>1.0.0.2</Version>
99
<Managed>0</Managed>
1010
<Publisher>
1111
<UniqueName>dataverse_samples</UniqueName>

media/sample_RetrieveOptions.png

13.7 KB
Loading

media/sample_RetrieveOptionsError.png

31.8 KB
Loading

0 commit comments

Comments
 (0)