Skip to content

Commit 22259f3

Browse files
Copilotmagesoe
andcommitted
Implement plugin step indicators for attributes
Co-authored-by: magesoe <8904582+magesoe@users.noreply.github.com>
1 parent f208144 commit 22259f3

5 files changed

Lines changed: 196 additions & 7 deletions

File tree

Generator/DTO/Attributes/Attribute.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public abstract class Attribute
77
public bool IsStandardFieldModified { get; set; }
88
public bool IsCustomAttribute { get; set; }
99
public bool IsPrimaryId { get; set; }
10+
public bool HasPluginStep { get; set; }
1011
public string DisplayName { get; }
1112
public string SchemaName { get; }
1213
public string Description { get; }
@@ -16,10 +17,11 @@ public abstract class Attribute
1617
public bool IsColumnSecured { get; }
1718
public CalculationMethods? CalculationMethod { get; }
1819

19-
protected Attribute(AttributeMetadata metadata)
20+
protected Attribute(AttributeMetadata metadata, bool hasPluginStep = false)
2021
{
2122
IsPrimaryId = metadata.IsPrimaryId ?? false;
2223
IsCustomAttribute = metadata.IsCustomAttribute ?? false;
24+
HasPluginStep = hasPluginStep;
2325
DisplayName = metadata.DisplayName.UserLocalizedLabel?.Label ?? string.Empty;
2426
SchemaName = metadata.SchemaName;
2527
Description = metadata.Description.UserLocalizedLabel?.Label.PrettyDescription() ?? string.Empty;

Generator/DataverseService.cs

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public async Task<IEnumerable<Record>> GetFilteredMetadata()
6161
).ToList());
6262

6363
var logicalNameToSecurityRoles = await GetSecurityRoles(rolesInSolution, entitiesInSolutionMetadata.ToDictionary(x => x.LogicalName, x => x.Privileges));
64+
var pluginStepAttributeMap = await GetPluginStepAttributes();
6465
var entityLogicalNamesInSolution = entitiesInSolutionMetadata.Select(e => e.LogicalName).ToHashSet();
6566

6667
logger.LogInformation("There are {Count} entities in the solution.", entityLogicalNamesInSolution.Count);
@@ -118,6 +119,7 @@ public async Task<IEnumerable<Record>> GetFilteredMetadata()
118119
securityRoles ?? [],
119120
keys ?? [],
120121
entityIconMap,
122+
pluginStepAttributeMap,
121123
configuration);
122124
});
123125
}
@@ -132,13 +134,16 @@ private static Record MakeRecord(
132134
List<SecurityRole> securityRoles,
133135
List<Key> keys,
134136
Dictionary<string, string> entityIconMap,
137+
Dictionary<string, HashSet<string>> pluginStepAttributeMap,
135138
IConfiguration configuration)
136139
{
137140
var attributes =
138141
relevantAttributes
139142
.Select(metadata =>
140143
{
141-
var attr = GetAttribute(metadata, entity, logicalToSchema, logger);
144+
pluginStepAttributeMap.TryGetValue(entity.LogicalName, out var entityPluginAttributes);
145+
var hasPluginStep = entityPluginAttributes?.Contains(metadata.LogicalName) == true;
146+
var attr = GetAttribute(metadata, entity, logicalToSchema, hasPluginStep, logger);
142147
attr.IsStandardFieldModified = MetadataExtensions.StandardFieldHasChanged(metadata, entity.DisplayName.UserLocalizedLabel?.Label ?? string.Empty);
143148
return attr;
144149
})
@@ -212,9 +217,9 @@ private static Record MakeRecord(
212217
iconBase64);
213218
}
214219

215-
private static Attribute GetAttribute(AttributeMetadata metadata, EntityMetadata entity, Dictionary<string, ExtendedEntityInformation> logicalToSchema, ILogger<DataverseService> logger)
220+
private static Attribute GetAttribute(AttributeMetadata metadata, EntityMetadata entity, Dictionary<string, ExtendedEntityInformation> logicalToSchema, bool hasPluginStep, ILogger<DataverseService> logger)
216221
{
217-
return metadata switch
222+
Attribute attr = metadata switch
218223
{
219224
PicklistAttributeMetadata picklist => new ChoiceAttribute(picklist),
220225
MultiSelectPicklistAttributeMetadata multiSelect => new ChoiceAttribute(multiSelect),
@@ -230,6 +235,8 @@ private static Attribute GetAttribute(AttributeMetadata metadata, EntityMetadata
230235
FileAttributeMetadata fileAttribute => new FileAttribute(fileAttribute),
231236
_ => new GenericAttribute(metadata)
232237
};
238+
attr.HasPluginStep = hasPluginStep;
239+
return attr;
233240
}
234241

235242
private static (string? Group, string? Description) GetGroupAndDescription(EntityMetadata entity, IDictionary<string, string> tableGroups)
@@ -539,6 +546,112 @@ private static string GetCoreUrl(string url)
539546
return $"{uri.Scheme}://{uri.Host}";
540547
}
541548

549+
private async Task<Dictionary<string, HashSet<string>>> GetPluginStepAttributes()
550+
{
551+
logger.LogInformation("Retrieving plugin step attributes...");
552+
553+
var pluginStepAttributeMap = new Dictionary<string, HashSet<string>>();
554+
555+
try
556+
{
557+
// Query sdkmessageprocessingstep table for steps with filtering attributes
558+
var stepQuery = new QueryExpression("sdkmessageprocessingstep")
559+
{
560+
ColumnSet = new ColumnSet("filteringattributes", "sdkmessagefilterid"),
561+
Criteria = new FilterExpression
562+
{
563+
Conditions =
564+
{
565+
new ConditionExpression("filteringattributes", ConditionOperator.NotNull),
566+
new ConditionExpression("filteringattributes", ConditionOperator.NotEqual, ""),
567+
new ConditionExpression("statecode", ConditionOperator.Equal, 0) // Only active steps
568+
}
569+
},
570+
LinkEntities =
571+
{
572+
new LinkEntity
573+
{
574+
LinkFromEntityName = "sdkmessageprocessingstep",
575+
LinkFromAttributeName = "sdkmessagefilterid",
576+
LinkToEntityName = "sdkmessagefilter",
577+
LinkToAttributeName = "sdkmessagefilterid",
578+
Columns = new ColumnSet("primaryobjecttypecode"),
579+
EntityAlias = "filter"
580+
}
581+
}
582+
};
583+
584+
var stepResults = await client.RetrieveMultipleAsync(stepQuery);
585+
586+
// Build a simple type code to entity name mapping using known common entities
587+
var typeCodeToLogicalName = new Dictionary<int, string>
588+
{
589+
{ 1, "account" },
590+
{ 2, "contact" },
591+
{ 3, "opportunity" },
592+
{ 4, "lead" },
593+
{ 5, "note" },
594+
{ 6, "businessunit" },
595+
{ 8, "systemuser" },
596+
{ 9, "team" },
597+
{ 10, "businessunitnewsarticle" },
598+
{ 14, "incident" },
599+
{ 112, "case" },
600+
{ 4200, "campaignactivity" },
601+
{ 4201, "list" },
602+
{ 4202, "campaign" },
603+
{ 4204, "fax" },
604+
{ 4207, "letter" },
605+
{ 4210, "phonecall" },
606+
{ 4212, "task" },
607+
{ 4214, "service" },
608+
{ 4216, "contract" },
609+
{ 4220, "workflow" }
610+
};
611+
612+
foreach (var step in stepResults.Entities)
613+
{
614+
var filteringAttributes = step.GetAttributeValue<string>("filteringattributes");
615+
var entityTypeCode = step.GetAttributeValue<AliasedValue>("filter.primaryobjecttypecode")?.Value as int?;
616+
617+
if (string.IsNullOrEmpty(filteringAttributes) || !entityTypeCode.HasValue)
618+
continue;
619+
620+
// Try to get entity logical name from our known mapping first
621+
string? logicalName = null;
622+
if (typeCodeToLogicalName.ContainsKey(entityTypeCode.Value))
623+
{
624+
logicalName = typeCodeToLogicalName[entityTypeCode.Value];
625+
}
626+
else
627+
{
628+
// For unknown type codes, we'll skip them for now
629+
// In a full implementation, you'd want to query the entity metadata
630+
logger.LogDebug("Unknown entity type code: {TypeCode}", entityTypeCode.Value);
631+
continue;
632+
}
633+
634+
if (!pluginStepAttributeMap.ContainsKey(logicalName))
635+
pluginStepAttributeMap[logicalName] = new HashSet<string>();
636+
637+
// Parse comma-separated attribute names
638+
var attributeNames = filteringAttributes.Split(',', StringSplitOptions.RemoveEmptyEntries);
639+
foreach (var attributeName in attributeNames)
640+
{
641+
pluginStepAttributeMap[logicalName].Add(attributeName.Trim());
642+
}
643+
}
644+
645+
logger.LogInformation("Found {Count} entities with plugin step attributes.", pluginStepAttributeMap.Count);
646+
}
647+
catch (Exception ex)
648+
{
649+
logger.LogWarning("Failed to retrieve plugin step attributes: {Message}", ex.Message);
650+
}
651+
652+
return pluginStepAttributeMap;
653+
}
654+
542655
private static async Task<AccessToken> FetchAccessToken(TokenCredential credential, string scope, ILogger logger)
543656
{
544657
var tokenRequestContext = new TokenRequestContext(new[] { scope });

Website/components/entity/AttributeDetails.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { AttributeType, CalculationMethods, RequiredLevel } from "@/lib/Types";
4-
import { Calculator, CircleAlert, CirclePlus, Eye, Lock, Sigma } from "lucide-react";
4+
import { Calculator, CircleAlert, CirclePlus, Eye, Lock, Sigma, Zap } from "lucide-react";
55
import { HybridTooltip, HybridTooltipContent, HybridTooltipTrigger } from "../ui/hybridtooltop";
66

77
export function AttributeDetails({ attribute }: { attribute: AttributeType }) {
@@ -34,6 +34,10 @@ export function AttributeDetails({ attribute }: { attribute: AttributeType }) {
3434
details.push({ icon: <Lock className="h-4 w-4" />, tooltip: "Field Security" });
3535
}
3636

37+
if (attribute.HasPluginStep) {
38+
details.push({ icon: <Zap className="h-4 w-4" />, tooltip: "Plugin Step" });
39+
}
40+
3741
return (
3842
<div className="flex flex-row gap-1">
3943
{details.map((detail, index) => (

Website/lib/Types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type BaseAttribute = {
4444
IsPrimaryId: boolean;
4545
IsCustomAttribute: boolean;
4646
IsStandardFieldModified: boolean;
47+
HasPluginStep: boolean;
4748
DisplayName: string,
4849
SchemaName: string,
4950
Description: string | null,

Website/stubs/Data.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,76 @@ export const Logo: string | null = null;
88

99
export let Groups: GroupType[] = [
1010
{
11-
"Name":"Untitled",
12-
"Entities":[]
11+
"Name": "Sample Data",
12+
"Entities": [
13+
{
14+
"DisplayName": "Account",
15+
"SchemaName": "Account",
16+
"Description": "Business organization or customer",
17+
"Group": "Sample Data",
18+
"IsAuditEnabled": true,
19+
"IsActivity": false,
20+
"IsNotesEnabled": true,
21+
"Ownership": 1,
22+
"Attributes": [
23+
{
24+
"AttributeType": "StringAttribute",
25+
"IsPrimaryId": false,
26+
"IsCustomAttribute": true,
27+
"IsStandardFieldModified": false,
28+
"HasPluginStep": true,
29+
"DisplayName": "Account Name",
30+
"SchemaName": "name",
31+
"Description": "The name of the account",
32+
"RequiredLevel": 2,
33+
"IsAuditEnabled": true,
34+
"IsColumnSecured": false,
35+
"CalculationMethod": null,
36+
"Format": "Text",
37+
"MaxLength": 160
38+
},
39+
{
40+
"AttributeType": "StringAttribute",
41+
"IsPrimaryId": false,
42+
"IsCustomAttribute": true,
43+
"IsStandardFieldModified": false,
44+
"HasPluginStep": false,
45+
"DisplayName": "Phone",
46+
"SchemaName": "telephone1",
47+
"Description": "The main phone number for the account",
48+
"RequiredLevel": 0,
49+
"IsAuditEnabled": false,
50+
"IsColumnSecured": false,
51+
"CalculationMethod": null,
52+
"Format": "Phone",
53+
"MaxLength": 50
54+
},
55+
{
56+
"AttributeType": "LookupAttribute",
57+
"IsPrimaryId": false,
58+
"IsCustomAttribute": true,
59+
"IsStandardFieldModified": false,
60+
"HasPluginStep": true,
61+
"DisplayName": "Primary Contact",
62+
"SchemaName": "primarycontactid",
63+
"Description": "The primary contact for the account",
64+
"RequiredLevel": 0,
65+
"IsAuditEnabled": true,
66+
"IsColumnSecured": false,
67+
"CalculationMethod": null,
68+
"Targets": [
69+
{
70+
"Name": "Contact",
71+
"IsInSolution": true
72+
}
73+
]
74+
}
75+
],
76+
"Relationships": [],
77+
"SecurityRoles": [],
78+
"Keys": [],
79+
"IconBase64": null
80+
}
81+
]
1382
}
1483
];

0 commit comments

Comments
 (0)