From 7d8b1b412cd5ad0163428c9a5790b8b23922fe4d Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 18 Jul 2024 15:42:22 -0500 Subject: [PATCH] Extract Dependency calculation to dedicated class --- .../ApiSchema/DependencyCalculator.cs | 93 +++++++++++++++++++ .../ApiService.cs | 79 +--------------- 2 files changed, 95 insertions(+), 77 deletions(-) create mode 100644 src/core/EdFi.DataManagementService.Core/ApiSchema/DependencyCalculator.cs diff --git a/src/core/EdFi.DataManagementService.Core/ApiSchema/DependencyCalculator.cs b/src/core/EdFi.DataManagementService.Core/ApiSchema/DependencyCalculator.cs new file mode 100644 index 000000000..7dd119b71 --- /dev/null +++ b/src/core/EdFi.DataManagementService.Core/ApiSchema/DependencyCalculator.cs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// Licensed to the Ed-Fi Alliance under one or more agreements. +// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. +// See the LICENSE and NOTICES files in the project root for more information. + +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging; + +namespace EdFi.DataManagementService.Core.ApiSchema; + +internal class DependencyCalculator(JsonNode _apiSchemaRootNode, ILogger _logger) +{ + public JsonArray GetDependencies() + { + var apiSchemaDocument = new ApiSchemaDocument(_apiSchemaRootNode, _logger); + var dependenciesJsonArray = new JsonArray(); + foreach (JsonNode projectSchemaNode in apiSchemaDocument.GetAllProjectSchemaNodes()) + { + var resourceSchemas = projectSchemaNode["resourceSchemas"]?.AsObject().Select(x => new ResourceSchema(x.Value!)).ToList()!; + + Dictionary> dependencies = + resourceSchemas.ToDictionary(rs => rs.ResourceName.Value, rs => new List()); + + foreach (var resourceSchema in resourceSchemas) + { + foreach (var documentPath in resourceSchema.DocumentPaths.Where(d => d.IsReference)) + { + dependencies[resourceSchema.ResourceName.Value].Add(documentPath.ResourceName.Value); + } + } + + List> orderedResources = []; + Dictionary visitedResources = []; + foreach (var dependency in dependencies.OrderBy(d => d.Value.Count).ThenBy(d => d.Key).Select(d => d.Key)) + { + RecursivelyDetermineDependencies(dependency, 0); + } + + foreach (var orderedResource in orderedResources.OrderBy(o => o.Value).ThenBy(o => o.Key)) + { + dependenciesJsonArray.Add(new { resource = $"/{projectSchemaNode!.GetPropertyName()}/{orderedResource.Key.First().ToString().ToLowerInvariant()}{orderedResource.Key.Substring(1)}", order = orderedResource.Value, operations = new[] { "Create", "Update" } }); + } + + int RecursivelyDetermineDependencies(string resourceName, int depth) + { + // Code Smell here: + // These resources are similar to abstract base classes, so they are not represented in the resourceSchemas + // portion of the schema document. This is a rudimentary replacement with the most specific version of the resource + if (resourceName == "EducationOrganization") + { + resourceName = "School"; + } + + if (resourceName == "GeneralStudentProgramAssociation") + { + resourceName = "StudentProgramAssociation"; + } + + if (!visitedResources.ContainsKey(resourceName)) + { + visitedResources.Add(resourceName, 0); + } + + var maxDepth = depth; + if (dependencies.ContainsKey(resourceName)) + { + foreach (var dependency in dependencies[resourceName]) + { + if (visitedResources.ContainsKey(dependency)) + { + if (visitedResources[dependency] > maxDepth) + { + maxDepth = visitedResources[dependency]; + } + } + else + { + var level = RecursivelyDetermineDependencies(dependency, depth); + if (level > maxDepth) + maxDepth = level; + } + } + orderedResources.Add(new KeyValuePair(resourceName, maxDepth + 1)); + visitedResources[resourceName] = maxDepth + 1; + } + + return maxDepth + 1; + } + } + + return dependenciesJsonArray; + } +} diff --git a/src/core/EdFi.DataManagementService.Core/ApiService.cs b/src/core/EdFi.DataManagementService.Core/ApiService.cs index 106a8c181..d552c294c 100644 --- a/src/core/EdFi.DataManagementService.Core/ApiService.cs +++ b/src/core/EdFi.DataManagementService.Core/ApiService.cs @@ -258,82 +258,7 @@ public IList GetDataModelInfo() /// JSON array ordered by dependency sequence public JsonArray GetDependencies() { - var apiSchemaDocument = new ApiSchemaDocument(_apiSchemaProvider.ApiSchemaRootNode, _logger); - var dependenciesJsonArray = new JsonArray(); - foreach (JsonNode projectSchemaNode in apiSchemaDocument.GetAllProjectSchemaNodes()) - { - var resourceSchemas = projectSchemaNode["resourceSchemas"]?.AsObject().Select(x => new ResourceSchema(x.Value!)).ToList()!; - - Dictionary> dependencies = - resourceSchemas.ToDictionary(rs => rs.ResourceName.Value, rs => new List()); - - foreach (var resourceSchema in resourceSchemas) - { - foreach (var documentPath in resourceSchema.DocumentPaths.Where(d => d.IsReference)) - { - dependencies[resourceSchema.ResourceName.Value].Add(documentPath.ResourceName.Value); - } - } - - List> orderedResources = []; - Dictionary visitedResources = []; - foreach (var dependency in dependencies.OrderBy(d => d.Value.Count).ThenBy(d => d.Key).Select(d => d.Key)) - { - RecursivelyDetermineDependencies(dependency, 0); - } - - foreach (var orderedResource in orderedResources.OrderBy(o => o.Value).ThenBy(o => o.Key)) - { - dependenciesJsonArray.Add(new { resource = $"/{projectSchemaNode!.GetPropertyName()}/{orderedResource.Key.First().ToString().ToLowerInvariant()}{orderedResource.Key.Substring(1)}", order = orderedResource.Value, operations = new[] { "Create", "Update" } }); - } - - int RecursivelyDetermineDependencies(string resourceName, int depth) - { - // Code Smell here: - // These resources are similar to abstract base classes, so they are not represented in the resourceSchemas - // portion of the schema document. This is a rudimentary replacement with the most specific version of the resource - if (resourceName == "EducationOrganization") - { - resourceName = "School"; - } - - if (resourceName == "GeneralStudentProgramAssociation") - { - resourceName = "StudentProgramAssociation"; - } - - if (!visitedResources.ContainsKey(resourceName)) - { - visitedResources.Add(resourceName, 0); - } - - var maxDepth = depth; - if (dependencies.ContainsKey(resourceName)) - { - foreach (var dependency in dependencies[resourceName]) - { - if (visitedResources.ContainsKey(dependency)) - { - if (visitedResources[dependency] > maxDepth) - { - maxDepth = visitedResources[dependency]; - } - } - else - { - var level = RecursivelyDetermineDependencies(dependency, depth); - if (level > maxDepth) - maxDepth = level; - } - } - orderedResources.Add(new KeyValuePair(resourceName, maxDepth + 1)); - visitedResources[resourceName] = maxDepth + 1; - } - - return maxDepth + 1; - } - } - - return dependenciesJsonArray; + var dependencyCalculator = new DependencyCalculator(_apiSchemaProvider.ApiSchemaRootNode, _logger); + return dependencyCalculator.GetDependencies(); } }