Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Generator/ClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,66 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;

namespace Generator;

public static class ClientExtensions
{
/// <summary>
/// Retrieves all pages of records matching the query, automatically following Dataverse pagination.
/// Dataverse returns at most 5000 records per page by default; this wrapper collects all pages.
/// </summary>
public static async Task<List<Entity>> RetrieveAllAsync(this ServiceClient client, QueryExpression query, CancellationToken cancellationToken = default)
{
var results = new List<Entity>();
query.PageInfo = new PagingInfo
{
Count = 5000,
PageNumber = 1,
PagingCookie = null,
ReturnTotalRecordCount = false
};

EntityCollection response;
do
{
response = await client.RetrieveMultipleAsync(query, cancellationToken);
results.AddRange(response.Entities);
query.PageInfo.PageNumber++;
query.PageInfo.PagingCookie = response.PagingCookie;
} while (response.MoreRecords);

return results;
}

/// <summary>
/// Synchronous variant of RetrieveAllAsync for use in non-async contexts.
/// </summary>
public static List<Entity> RetrieveAll(this ServiceClient client, QueryExpression query)
{
var results = new List<Entity>();
query.PageInfo = new PagingInfo
{
Count = 5000,
PageNumber = 1,
PagingCookie = null,
ReturnTotalRecordCount = false
};

EntityCollection response;
do
{
response = client.RetrieveMultiple(query);
results.AddRange(response.Entities);
query.PageInfo.PageNumber++;
query.PageInfo.PagingCookie = response.PagingCookie;
} while (response.MoreRecords);

return results;
}

public static async Task<EntityMetadata> RetrieveEntityAsync(this ServiceClient client, Guid objectId, CancellationToken token)
{
var resp = await client.ExecuteAsync(new RetrieveEntityRequest()
Expand Down
6 changes: 3 additions & 3 deletions Generator/DataverseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ public DataverseService(
IEnumerable<ComponentInfo> solutionComponents;
try
{
logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Calling solutionComponentService.GetAllSolutionComponents()");
solutionComponents = solutionComponentService.GetAllSolutionComponents(solutionIds);
logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Calling solutionComponentService.GetAllSolutionComponentsAsync()");
solutionComponents = await solutionComponentService.GetAllSolutionComponentsAsync(solutionIds);
logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Retrieved {solutionComponents.Count()} solution components");
}
catch (Exception ex)
Expand Down Expand Up @@ -219,7 +219,7 @@ public DataverseService(

// Get workflow dependencies for attributes (returns attribute ObjectId -> list of workflow ObjectIds)
var explicitComponentsList = solutionComponents.ToList();
var workflowDependencyMap = solutionComponentService.GetWorkflowDependenciesForAttributes(
var workflowDependencyMap = await solutionComponentService.GetWorkflowDependenciesForAttributesAsync(
explicitComponentsList.Where(c => c.ComponentType == 2).Select(c => new SolutionComponentInfo(
c.ObjectId,
c.SolutionComponentId ?? Guid.Empty,
Expand Down
4 changes: 2 additions & 2 deletions Generator/Queries/PluginQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ public static async Task<IEnumerable<SDKStep>> GetSDKMessageProcessingStepsAsync


//if (solutionIds is not null) query.Criteria.Conditions.Add(new ConditionExpression("solutionid", ConditionOperator.In, solutionIds));
var result = await service.RetrieveMultipleAsync(query);
var result = await service.RetrieveAllAsync(query);

var steps = result.Entities.Select(e =>
var steps = result.Select(e =>
{
var sdkMessageId = e.GetAttributeValue<AliasedValue>("step.sdkmessageid")?.Value as EntityReference;
var sdkStepName = e.GetAttributeValue<AliasedValue>("step.name")?.Value as string;
Expand Down
4 changes: 2 additions & 2 deletions Generator/Queries/PowerAutomateQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@
}
};

var result = await service.RetrieveMultipleAsync(query);
var result = await service.RetrieveAllAsync(query);

var flows = result.Entities.Select(e =>
var flows = result.Select(e =>
{
return new PowerAutomateFlow(
e.GetAttributeValue<AliasedValue>("flow.workflowid").Value as string,

Check warning on line 50 in Generator/Queries/PowerAutomateQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'Id' in 'PowerAutomateFlow.PowerAutomateFlow(string Id, string Name, string ClientData)'.

Check warning on line 50 in Generator/Queries/PowerAutomateQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'Id' in 'PowerAutomateFlow.PowerAutomateFlow(string Id, string Name, string ClientData)'.
e.GetAttributeValue<AliasedValue>("flow.name").Value as string,

Check warning on line 51 in Generator/Queries/PowerAutomateQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'Name' in 'PowerAutomateFlow.PowerAutomateFlow(string Id, string Name, string ClientData)'.

Check warning on line 51 in Generator/Queries/PowerAutomateQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'Name' in 'PowerAutomateFlow.PowerAutomateFlow(string Id, string Name, string ClientData)'.
e.GetAttributeValue<AliasedValue>("flow.clientdata").Value as string

Check warning on line 52 in Generator/Queries/PowerAutomateQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'ClientData' in 'PowerAutomateFlow.PowerAutomateFlow(string Id, string Name, string ClientData)'.
);
});

Expand Down
2 changes: 1 addition & 1 deletion Generator/Queries/WebResourceQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
}
};

var results = (await service.RetrieveMultipleAsync(query)).Entities;
var results = await service.RetrieveAllAsync(query);

var webResources = results.Select(e =>
{
Expand Down Expand Up @@ -73,7 +73,7 @@

return new WebResource(
webresourceId,
webresourceName,

Check warning on line 76 in Generator/Queries/WebResourceQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'Name' in 'WebResource.WebResource(string Id, string Name, string Content, OptionSetValue WebResourceType, string Description)'.

Check warning on line 76 in Generator/Queries/WebResourceQueries.cs

View workflow job for this annotation

GitHub Actions / generator

Possible null reference argument for parameter 'Name' in 'WebResource.WebResource(string Id, string Name, string Content, OptionSetValue WebResourceType, string Description)'.
content,
(OptionSetValue)e.GetAttributeValue<AliasedValue>("webresource.webresourcetype").Value,
webresourceDesc
Expand Down
4 changes: 2 additions & 2 deletions Generator/Services/EntityIconService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public async Task<Dictionary<string, string>> GetEntityIconMap(IEnumerable<Entit
}
};

var webresources = await client.RetrieveMultipleAsync(query);
iconNameToSvg = webresources.Entities.ToDictionary(x => x.GetAttributeValue<string>("name"), x => x.GetAttributeValue<string>("content"));
var webresources = await client.RetrieveAllAsync(query);
iconNameToSvg = webresources.ToDictionary(x => x.GetAttributeValue<string>("name"), x => x.GetAttributeValue<string>("content"));
}

var logicalNameToSvg =
Expand Down
4 changes: 2 additions & 2 deletions Generator/Services/SecurityRoleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ public async Task<Dictionary<string, List<SecurityRole>>> GetSecurityRoles(
}
};

var roles = await client.RetrieveMultipleAsync(query);
var roles = await client.RetrieveAllAsync(query);

var rolePrivileges = roles.Entities.Select(e =>
var rolePrivileges = roles.Select(e =>
{
var name = e.GetAttributeValue<string>("name");
var depth = (PrivilegeDepth)e.GetAttributeValue<AliasedValue>("rolepriv.privilegedepthmask").Value;
Expand Down
8 changes: 4 additions & 4 deletions Generator/Services/SolutionComponentExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ private async Task<List<RawComponentInfo>> QuerySolutionComponentsAsync(List<Gui

try
{
var response = await _client.RetrieveMultipleAsync(query);
foreach (var entity in response.Entities)
var entities = await _client.RetrieveAllAsync(query);
foreach (var entity in entities)
{
var componentType = entity.GetAttributeValue<OptionSetValue>("componenttype")?.Value ?? 0;
var objectId = entity.GetAttributeValue<Guid>("objectid");
Expand Down Expand Up @@ -315,8 +315,8 @@ private async Task<List<RawComponentInfo>> QuerySolutionComponentsAsync(List<Gui
}
};

var response = await _client.RetrieveMultipleAsync(query);
foreach (var entity in response.Entities)
var entities = await _client.RetrieveAllAsync(query);
foreach (var entity in entities)
{
var id = entity.GetAttributeValue<Guid>(primaryKey);
var name = entity.GetAttributeValue<string>(nameColumn) ?? id.ToString();
Expand Down
32 changes: 16 additions & 16 deletions Generator/Services/SolutionComponentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public SolutionComponentService(ServiceClient client, IConfiguration configurati
_logger = logger;
}

public IEnumerable<ComponentInfo> GetAllSolutionComponents(List<Guid> solutionIds)
public async Task<IEnumerable<ComponentInfo>> GetAllSolutionComponentsAsync(List<Guid> solutionIds)
{
_logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Getting all solution components for solutions: {string.Join(", ", solutionIds)}");
var allComponents = new HashSet<ComponentInfo>();
Expand All @@ -74,7 +74,7 @@ public IEnumerable<ComponentInfo> GetAllSolutionComponents(List<Guid> solutionId
IEnumerable<SolutionComponentInfo> explicitComponents;
try
{
explicitComponents = GetExplicitComponents(solutionIds);
explicitComponents = await GetExplicitComponentsAsync(solutionIds);
_logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Found {explicitComponents.Count()} explicit components");
}
catch (Exception ex)
Expand Down Expand Up @@ -118,7 +118,7 @@ public IEnumerable<ComponentInfo> GetAllSolutionComponents(List<Guid> solutionId
_logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Retrieving component information for {requiredNodeIds.Count} dependency nodes");

// Retrieve component node information
var componentNodes = GetComponentNodes(requiredNodeIds);
var componentNodes = await GetComponentNodesAsync(requiredNodeIds);
_logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Retrieved {componentNodes.Count} component nodes");

// Add required components as implicit
Expand Down Expand Up @@ -152,7 +152,7 @@ public IEnumerable<ComponentInfo> GetAllSolutionComponents(List<Guid> solutionId
return allComponents;
}

private IEnumerable<SolutionComponentInfo> GetExplicitComponents(List<Guid> solutionIds)
private async Task<IEnumerable<SolutionComponentInfo>> GetExplicitComponentsAsync(List<Guid> solutionIds)
{
var query = new QueryExpression("solutioncomponent")
{
Expand All @@ -167,16 +167,16 @@ private IEnumerable<SolutionComponentInfo> GetExplicitComponents(List<Guid> solu
}
};

return _client.RetrieveMultiple(query).Entities
.Select(e => new SolutionComponentInfo(
e.GetAttributeValue<Guid>("objectid"),
e.GetAttributeValue<Guid>("solutioncomponentid"),
e.GetAttributeValue<OptionSetValue>("componenttype").Value,
e.Contains("rootcomponentbehavior") ? e.GetAttributeValue<OptionSetValue>("rootcomponentbehavior").Value : -1,
e.GetAttributeValue<EntityReference>("solutionid")));
var entities = await _client.RetrieveAllAsync(query);
return entities.Select(e => new SolutionComponentInfo(
e.GetAttributeValue<Guid>("objectid"),
e.GetAttributeValue<Guid>("solutioncomponentid"),
e.GetAttributeValue<OptionSetValue>("componenttype").Value,
e.Contains("rootcomponentbehavior") ? e.GetAttributeValue<OptionSetValue>("rootcomponentbehavior").Value : -1,
e.GetAttributeValue<EntityReference>("solutionid")));
}

private List<ComponentNodeInfo> GetComponentNodes(List<Guid> nodeIds)
private async Task<List<ComponentNodeInfo>> GetComponentNodesAsync(List<Guid> nodeIds)
{
if (!nodeIds.Any())
{
Expand All @@ -198,8 +198,8 @@ private List<ComponentNodeInfo> GetComponentNodes(List<Guid> nodeIds)

try
{
var response = _client.RetrieveMultiple(query);
foreach (var entity in response.Entities)
var entities = await _client.RetrieveAllAsync(query);
foreach (var entity in entities)
{
results.Add(new ComponentNodeInfo(
entity.GetAttributeValue<Guid>("dependencynodeid"),
Expand Down Expand Up @@ -374,7 +374,7 @@ private HashSet<DependencyInfo> GetRequiredComponents(IEnumerable<SolutionCompon
/// </summary>
/// <param name="attributeComponents">List of attribute components to check for dependencies</param>
/// <returns>Dictionary mapping attribute ObjectId to list of workflow ObjectIds that depend on it</returns>
public Dictionary<Guid, List<Guid>> GetWorkflowDependenciesForAttributes(IEnumerable<SolutionComponentInfo> attributeComponents)
public async Task<Dictionary<Guid, List<Guid>>> GetWorkflowDependenciesForAttributesAsync(IEnumerable<SolutionComponentInfo> attributeComponents)
{
var workflowDependencies = new Dictionary<Guid, List<Guid>>();

Expand Down Expand Up @@ -406,7 +406,7 @@ public Dictionary<Guid, List<Guid>> GetWorkflowDependenciesForAttributes(IEnumer
return workflowDependencies;

// Retrieve component node information for all nodes
var allNodes = GetComponentNodes(allNodeIds);
var allNodes = await GetComponentNodesAsync(allNodeIds);

// Filter to only workflow components (type 29)
var workflowNodes = allNodes.Where(n => n.ComponentType == 29).ToList();
Expand Down
8 changes: 4 additions & 4 deletions Generator/Services/SolutionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public SolutionService(ServiceClient client, IConfiguration configuration, ILogg
}
var solutionNames = solutionNameArg.Split(",").Select(x => x.Trim().ToLower()).ToList();

var resp = await client.RetrieveMultipleAsync(new QueryExpression("solution")
var entities = await client.RetrieveAllAsync(new QueryExpression("solution")
{
ColumnSet = new ColumnSet("publisherid", "friendlyname", "uniquename", "solutionid"),
Criteria = new FilterExpression(LogicalOperator.And)
Expand All @@ -46,7 +46,7 @@ public SolutionService(ServiceClient client, IConfiguration configuration, ILogg
}
});

return (resp.Entities.Select(e => e.GetAttributeValue<Guid>("solutionid")).ToList(), resp.Entities.ToList());
return (entities.Select(e => e.GetAttributeValue<Guid>("solutionid")).ToList(), entities);
}

/// <summary>
Expand All @@ -73,8 +73,8 @@ public SolutionService(ServiceClient client, IConfiguration configuration, ILogg
}
};

var publishers = await client.RetrieveMultipleAsync(publisherQuery);
return publishers.Entities.ToDictionary(
var publishers = await client.RetrieveAllAsync(publisherQuery);
return publishers.ToDictionary(
p => p.GetAttributeValue<Guid>("publisherid"),
p => (
Name: p.GetAttributeValue<string>("friendlyname") ?? "Unknown Publisher",
Expand Down
4 changes: 2 additions & 2 deletions Generator/Services/WorkflowService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public async Task<Dictionary<Guid, WorkflowInfo>> GetWorkflows(List<Guid> workfl
}
};

var results = await client.RetrieveMultipleAsync(query);
var results = await client.RetrieveAllAsync(query);

return results.Entities.ToDictionary(
return results.ToDictionary(
e => e.Id,
e => new WorkflowInfo(
e.Id,
Expand Down
5 changes: 2 additions & 3 deletions Generator/WebsiteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Generator.DTO.Warnings;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Text;

namespace Generator;
Expand Down Expand Up @@ -64,7 +63,7 @@ internal void AddData()
sb.AppendLine(" },");
}

sb.AppendLine("]");
sb.AppendLine("] as const;");

// WARNINGS
sb.AppendLine("");
Expand All @@ -73,7 +72,7 @@ internal void AddData()
{
sb.AppendLine($" {JsonConvert.SerializeObject(warning)},");
}
sb.AppendLine("]");
sb.AppendLine("] as const;");

// SOLUTION COMPONENTS (for insights)
sb.AppendLine("");
Expand Down
Loading