Skip to content

Commit 9d2cd2d

Browse files
authored
Merge pull request #61 from delegateas/patches/webresource-analyzer
Analyzer Changes
2 parents 6ec1404 + f40e46f commit 9d2cd2d

7 files changed

Lines changed: 232 additions & 203 deletions

File tree

Generator/Services/Power Automate/PowerAutomateFlowAnalyzer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ private async Task AnalyzeFlowDefinition(JObject flowDefinition, PowerAutomateFl
4444
foreach (var action in actions)
4545
await AnalyzeAction(action, flow, attributeUsages);
4646

47-
// Also check for dynamic content references that might reference Dataverse fields
48-
AnalyzeDynamicContent(flowDefinition, flow, attributeUsages);
47+
// Also check for dynamic content references that might reference Dataverse fields - problematic to do, as you can known if what is inside the query are CDS props
48+
// AnalyzeDynamicContent(flowDefinition, flow, attributeUsages);
4949
}
5050

5151
private IEnumerable<JToken> ExtractActions(JObject flowDefinition)
@@ -396,7 +396,7 @@ private bool IsLikelyFieldName(string name)
396396
{
397397
"@odata.context", "@odata.etag", "@odata.id", "@odata.type",
398398
"entityName", "entitySetName", "uri", "path", "method", "headers",
399-
"authentication", "retryPolicy", "pagination", "timeout"
399+
"authentication", "retryPolicy", "pagination", "timeout", "recordId"
400400
};
401401

402402
if (systemFields.Contains(name)) return false;

Generator/Services/WebResources/WebResourceAnalyzer.cs

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ namespace Generator.Services.WebResources;
88

99
public class WebResourceAnalyzer : BaseComponentAnalyzer<WebResource>
1010
{
11+
private record AttributeCall(string AttributeName, string Type, OperationType Operation);
12+
1113
private readonly Func<string, string> webresourceNamingFunc;
1214
public WebResourceAnalyzer(ServiceClient service, IConfiguration configuration) : base(service)
1315
{
14-
var lambda = configuration.GetValue<string>("WebResourceNameFunc") ?? "name.Split('.').First()";
16+
var lambda = configuration.GetValue<string>("WebResourceNameFunc") ?? "np(name.Split('/').LastOrDefault()).Split('.').Reverse().Skip(1).FirstOrDefault()";
1517
webresourceNamingFunc = DynamicExpressionParser.ParseLambda<string, string>(
1618
new ParsingConfig { ResolveTypesBySimpleName = true },
1719
false,
@@ -43,11 +45,14 @@ private void AnalyzeOnChangeHandlers(WebResource webResource, Dictionary<string,
4345
{
4446
var content = webResource.Content;
4547

46-
var attributeNames = ExtractGetAttributeCalls(content);
48+
var attributeNames = new List<AttributeCall>();
49+
ExtractGetAttributeCalls(content, attributeNames);
50+
ExtractGetControlCalls(content, attributeNames);
51+
// TODO get attributes used in XrmApi or XrmQuery calls
4752

4853
foreach (var attributeName in attributeNames)
4954
{
50-
string entityName = null;
55+
string entityName;
5156
try
5257
{
5358
entityName = webresourceNamingFunc(webResource.Name);
@@ -62,41 +67,58 @@ private void AnalyzeOnChangeHandlers(WebResource webResource, Dictionary<string,
6267
Console.WriteLine($"Warning: Naming function returned an invalid value for web resource '{webResource.Name}'. Skipping attribute usage.");
6368
continue;
6469
}
65-
AddAttributeUsage(attributeUsages, entityName.ToLower(), attributeName, new AttributeUsage(
70+
AddAttributeUsage(attributeUsages, entityName.ToLower(), attributeName.AttributeName, new AttributeUsage(
6671
webResource.Name,
67-
$"getAttribute call",
68-
OperationType.Read,
72+
attributeName.Type,
73+
attributeName.Operation,
6974
SupportedType
7075
));
7176
}
7277
}
7378

74-
// TODO get attributes used in XrmApi or XrmQuery calls
75-
76-
// TODO get attributes from getControl
77-
78-
private List<string> ExtractGetAttributeCalls(string code)
79+
private void ExtractGetAttributeCalls(string code, List<AttributeCall> attributes)
7980
{
80-
var attributes = new List<string>();
81-
8281
if (string.IsNullOrEmpty(code))
83-
return attributes;
82+
return;
8483

8584
// Examples:
86-
// formContext.getAttribute("firstname")
87-
// Xrm.Page.getAttribute("lastname")
85+
// formContext.getAttribute("firstname").setValue("some value")
86+
// Xrm.Page.getAttribute("lastname").getValue()
8887
// executionContext.getFormContext().getAttribute("email")
8988
// context.getAttribute("phonenumber")
9089
// this.getAttribute("address1_city")
91-
var getAttributePattern = @"(\w+(?:\.\w+)*\.getAttribute)\([""']([^""']+)[""']\)";
90+
var getAttributePattern = @"(?<recv>\b\w+(?:\.\w+)*\.getAttribute)\(\s*[""'](?<attr>[^""']+)[""']\s*\)(?:\s*\.\s*(?<op>getValue|setValue)\s*\((?<args>[^)]*)\))?";
9291
var matches = Regex.Matches(code, getAttributePattern, RegexOptions.IgnoreCase);
9392

9493
foreach (Match match in matches)
9594
{
96-
var attributeName = match.Groups[2].Value;
97-
attributes.Add(attributeName);
95+
var attributeName = match.Groups["attr"].Value;
96+
var operation = match.Groups["op"].Value.StartsWith("get") ? OperationType.Read : OperationType.Update;
97+
attributes.Add(new AttributeCall(attributeName, $"getAttribute call, {operation}", operation));
9898
}
99+
return;
100+
}
101+
102+
// getControl calls also return tabs, subgrids etc. which is a bit problematic
103+
private void ExtractGetControlCalls(string code, List<AttributeCall> attributes)
104+
{
105+
if (string.IsNullOrEmpty(code))
106+
return;
107+
108+
// Examples:
109+
// formContext.getControl("firstname")
110+
// Xrm.Page.getControl("lastname")
111+
// executionContext.getFormContext().getControl("email")
112+
// context.getControl("phonenumber")
113+
// this.getControl("address1_city")
114+
var getAttributePattern = @"(?<recv>\b\w+(?:\.\w+)*\.getControl)\(\s*[""'](?<attr>[^""']+)[""']?";
115+
var matches = Regex.Matches(code, getAttributePattern, RegexOptions.IgnoreCase);
99116

100-
return attributes.Distinct().ToList();
117+
foreach (Match match in matches)
118+
{
119+
var attributeName = match.Groups["attr"].Value;
120+
attributes.Add(new AttributeCall(attributeName, "getControl call", OperationType.Read));
121+
}
122+
return;
101123
}
102124
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ The pipeline expects a variable group called `DataModel`. It must have the follo
9292
* (Optional) TableGroups: Enter a semi-colon separated list of group names and for each group a comma-separated list of table schema names within that group. Then this configuration will be used to order the tables in groups in the DMV side-menu. Example: `Org. tables: team, systemuser, businessunit; Sales: opportunity, lead`
9393
* (Optional) AdoWikiName: Name of your wiki found under "Overview -> Wiki" in ADO. (will be encoded so dont worry about space)
9494
* (Optional) AdoWikiPagePath: Path to the introduction page you wish to show in DMV. (will also be encoded so dont worry about spaces)
95-
* (Optional) WebResourceNameFunc: Function to fetch the entity logicalname from a webresource. The function must be a valid C# LINQ expression that works on the `name` input parameter. Default: `name.Split('.').First()`
95+
* (Optional) WebResourceNameFunc: Function to fetch the entity logicalname from a webresource. The function must be a valid C# LINQ expression that works on the `name` input parameter. Default: `np(name.Split('/').LastOrDefault()).Split('.').Reverse().Skip(1).FirstOrDefault()`
9696

9797
## After deployment
9898
* Go to portal.azure.com

Website/components/datamodelview/entity/AttributeDetails.tsx

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

33
import { AttributeType, CalculationMethods, ComponentType, RequiredLevel } from "@/lib/Types";
4-
import { AddCircleOutlineRounded, CalculateRounded, ElectricBoltRounded, ErrorRounded, FunctionsRounded, LockRounded, VisibilityRounded } from "@mui/icons-material";
4+
import { AccountTreeRounded, AddCircleOutlineRounded, CalculateRounded, ElectricBoltRounded, ErrorRounded, FunctionsRounded, JavascriptRounded, LockRounded, VisibilityRounded } from "@mui/icons-material";
55
import { Tooltip } from "@mui/material";
66

77
export function AttributeDetails({ attribute }: { attribute: AttributeType }) {
@@ -35,10 +35,20 @@ export function AttributeDetails({ attribute }: { attribute: AttributeType }) {
3535
}
3636

3737
if (attribute.AttributeUsages.some(a => a.ComponentType == ComponentType.Plugin)) {
38-
const tooltip = `Plugins ${attribute.AttributeUsages.map(au => au.Name).join(", ")}`;
38+
const tooltip = `Plugins ${attribute.AttributeUsages.filter(au => au.ComponentType == ComponentType.Plugin).map(au => au.Name).join(", ")}`;
3939
details.push({ icon: <ElectricBoltRounded className="h-4 w-4" />, tooltip });
4040
}
4141

42+
if (attribute.AttributeUsages.some(a => a.ComponentType == ComponentType.PowerAutomateFlow)) {
43+
const tooltip = `Power Automate Flows ${attribute.AttributeUsages.filter(au => au.ComponentType == ComponentType.PowerAutomateFlow).map(au => au.Name).join(", ")}`;
44+
details.push({ icon: <AccountTreeRounded className="h-4 w-4" />, tooltip });
45+
}
46+
47+
if (attribute.AttributeUsages.some(a => a.ComponentType == ComponentType.WebResource)) {
48+
const tooltip = `Web Resources ${attribute.AttributeUsages.filter(au => au.ComponentType == ComponentType.WebResource).map(au => au.Name).join(", ")}`;
49+
details.push({ icon: <JavascriptRounded className="h-4 w-4" />, tooltip });
50+
}
51+
4252
return (
4353
<div className="flex flex-row gap-1">
4454
{details.map((detail, index) => (

Website/components/processesview/ProcessesView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export const ProcessesView = ({ }: IProcessesViewProps) => {
193193
title="attribute usages"
194194
value={typeDistribution[ComponentType.WebResource] || 0}
195195
highlightedWord="Web Resource"
196-
tooltipTitle="Only includes getAttribute from Web Resource."
196+
tooltipTitle="Only includes getAttribute/getControl from Web Resource."
197197
tooltipWarning="Limitations"
198198
imageSrc="/webresource.svg"
199199
imageAlt="Web Resource icon"
@@ -418,7 +418,7 @@ export const ProcessesView = ({ }: IProcessesViewProps) => {
418418
</Box>
419419
) : (
420420
<Box
421-
className="overflow-x-auto md:overflow-x-visible"
421+
className="overflow-x-auto md:overflow-x-visible rounded-b-2xl"
422422
sx={{
423423
borderTop: 1,
424424
borderColor: 'border.main',
@@ -443,7 +443,7 @@ export const ProcessesView = ({ }: IProcessesViewProps) => {
443443
>
444444
<Table
445445
stickyHeader
446-
className="w-full min-w-[600px] md:min-w-0"
446+
className="w-full min-w-[600px] md:min-w-0 rounded-b-2xl"
447447
sx={{
448448
borderColor: 'border.main'
449449
}}

0 commit comments

Comments
 (0)