public async Task <IActionResult> GetCurrentDatasetRelationshipFieldsBySpecificationId(string specificationId)
        {
            Guard.IsNullOrWhiteSpace(specificationId, nameof(specificationId));

            IEnumerable <DefinitionSpecificationRelationship> relationships =
                (await _datasetRepository.GetDefinitionSpecificationRelationshipsByQuery(m => m.Specification.Id == specificationId)).ToList();

            if (relationships.IsNullOrEmpty())
            {
                relationships = new DefinitionSpecificationRelationship[0];
            }

            IEnumerable <string> definitionIds = relationships.Select(m => m.DatasetDefinition.Id);

            IEnumerable <DatasetDefinition> definitions = await _datasetRepository.GetDatasetDefinitionsByQuery(d => definitionIds.Contains(d.Id));

            IList <DatasetSchemaRelationshipModel> schemaRelationshipModels = new List <DatasetSchemaRelationshipModel>();

            foreach (DefinitionSpecificationRelationship definitionSpecificationRelationship in relationships)
            {
                string            relationshipName  = definitionSpecificationRelationship.Name;
                string            datasetName       = VisualBasicTypeGenerator.GenerateIdentifier(relationshipName);
                DatasetDefinition datasetDefinition = definitions.FirstOrDefault(m => m.Id == definitionSpecificationRelationship.DatasetDefinition.Id);

                schemaRelationshipModels.Add(new DatasetSchemaRelationshipModel
                {
                    DefinitionId     = datasetDefinition.Id,
                    RelationshipId   = definitionSpecificationRelationship.Id,
                    RelationshipName = relationshipName,
                    Fields           = datasetDefinition.TableDefinitions.SelectMany(m => m.FieldDefinitions.Select(f => new DatasetSchemaRelationshipField
                    {
                        Name                   = f.Name,
                        SourceName             = VisualBasicTypeGenerator.GenerateIdentifier(f.Name),
                        SourceRelationshipName = datasetName,
                        IsAggregable           = f.IsAggregable
                    }))
                });
            }

            return(new OkObjectResult(schemaRelationshipModels));
        }
Beispiel #2
0
        public async Task ResetScenarioForFieldDefinitionChanges(IEnumerable <DatasetSpecificationRelationshipViewModel> relationships, string specificationId, IEnumerable <string> currentFieldDefinitionNames)
        {
            Guard.ArgumentNotNull(relationships, nameof(relationships));
            Guard.IsNullOrWhiteSpace(specificationId, nameof(specificationId));
            Guard.ArgumentNotNull(currentFieldDefinitionNames, nameof(currentFieldDefinitionNames));

            IEnumerable <TestScenario> scenarios = await _scenariosRepositoryPolicy.ExecuteAsync(() => _scenariosRepository.GetTestScenariosBySpecificationId(specificationId));

            if (scenarios.IsNullOrEmpty())
            {
                _logger.Information($"No scenarios found for specification id '{specificationId}'");
                return;
            }

            List <string> fieldIdentifiers = new List <string>();

            foreach (DatasetSpecificationRelationshipViewModel datasetSpecificationRelationshipViewModel in relationships)
            {
                fieldIdentifiers.AddRange(currentFieldDefinitionNames.Select(m => $"dataset {datasetSpecificationRelationshipViewModel.Name} field {VisualBasicTypeGenerator.GenerateIdentifier(m)}"));
            }

            IEnumerable <TestScenario> scenariosToUpdate = scenarios.Where(m => SourceCodeHelpers.CodeContainsFullyQualifiedDatasetFieldIdentifier(m.Current.Gherkin.RemoveAllQuotes(), fieldIdentifiers));

            if (scenariosToUpdate.IsNullOrEmpty())
            {
                _logger.Information($"No test scenarios required resetting for specification id '{specificationId}'");
                return;
            }

            const string reasonForCommenting = "The dataset definition referenced by this scenario/spec has been updated and subsequently the code has been commented out";

            foreach (TestScenario scenario in scenariosToUpdate)
            {
                string gherkin = scenario.Current.Gherkin;

                string updatedGherkin = SourceCodeHelpers.CommentOutCode(gherkin, reasonForCommenting, commentSymbol: "#");

                await SaveVersion(scenario, updatedGherkin);
            }
        }
        private async Task ProcessFieldChanges(string datasetDefinitionId, IEnumerable <FieldDefinitionChanges> fieldChanges, IEnumerable <string> relationshipSpecificationIds)
        {
            Guard.IsNullOrWhiteSpace(datasetDefinitionId, nameof(datasetDefinitionId));
            Guard.ArgumentNotNull(fieldChanges, nameof(fieldChanges));
            Guard.ArgumentNotNull(relationshipSpecificationIds, nameof(relationshipSpecificationIds));

            IEnumerable <IGrouping <string, FieldDefinitionChanges> > groupedFieldChanges = fieldChanges.GroupBy(f => f.FieldDefinition.Id);

            IList <FieldDefinitionChanges> fieldDefinitionChanges = new List <FieldDefinitionChanges>();

            bool shouldResetCalculation = false;

            foreach (IGrouping <string, FieldDefinitionChanges> grouping in groupedFieldChanges)
            {
                FieldDefinitionChanges fieldDefinitionChange = grouping.FirstOrDefault(m => m.ChangeTypes.Any(
                                                                                           c => c == FieldDefinitionChangeType.FieldName) || m.RequiresRemap);

                if (fieldDefinitionChange != null)
                {
                    fieldDefinitionChanges.Add(fieldDefinitionChange);
                }

                shouldResetCalculation = true;
            }

            if (!shouldResetCalculation)
            {
                return;
            }

            foreach (string specificationId in relationshipSpecificationIds)
            {
                IEnumerable <DatasetSpecificationRelationshipViewModel> relationships = await _datasetRepositoryPolicy.ExecuteAsync(() => _datasetRepository.GetCurrentRelationshipsBySpecificationIdAndDatasetDefinitionId(specificationId, datasetDefinitionId));

                if (relationships.IsNullOrEmpty())
                {
                    throw new RetriableException($"No relationships found for specificationId '{specificationId}' and dataset definition id '{datasetDefinitionId}'");
                }

                IEnumerable <Calculation> calculations = (await _calculationsRepositoryPolicy.ExecuteAsync(() => _calculationsRepository.GetCalculationsBySpecificationId(specificationId))).ToList();

                IEnumerable <string> aggregateParameters = calculations.SelectMany(m => SourceCodeHelpers.GetDatasetAggregateFunctionParameters(m.Current.SourceCode));

                HashSet <string> fieldNames = new HashSet <string>();

                foreach (FieldDefinitionChanges changes in fieldDefinitionChanges)
                {
                    //Check if only aggregable changes
                    if (!changes.ChangeTypes.Contains(FieldDefinitionChangeType.FieldType) && !changes.ChangeTypes.Contains(FieldDefinitionChangeType.FieldName))
                    {
                        foreach (DatasetSpecificationRelationshipViewModel datasetSpecificationRelationshipViewModel in relationships)
                        {
                            if (aggregateParameters.Contains($"Datasets.{VisualBasicTypeGenerator.GenerateIdentifier(datasetSpecificationRelationshipViewModel.Name)}.{VisualBasicTypeGenerator.GenerateIdentifier(changes.ExistingFieldDefinition.Name)}"))
                            {
                                fieldNames.Add(changes.ExistingFieldDefinition.Name);
                            }
                        }
                    }
                    else
                    {
                        fieldNames.Add(changes.ExistingFieldDefinition.Name);
                    }
                }

                if (fieldNames.Any())
                {
                    await _calculationService.ResetCalculationForFieldDefinitionChanges(relationships, specificationId, fieldNames);
                }
            }
        }
Beispiel #4
0
        public async Task UpdateCalculationCodeOnCalculationSpecificationChange_WhenNoCalculationsFoundReferencingCalculationToBeUpdated_ThenNoCalculationsUpdated()
        {
            // Arrange
            ICalculationsRepository  calculationsRepository           = CreateCalculationsRepository();
            ISpecificationRepository specificationRepository          = CreateSpecificationRepository();
            IVersionRepository <CalculationVersion> versionRepository = CreateCalculationVersionRepository();
            ICalculationCodeReferenceUpdate         calculationCodeReferenceUpdate = FakeCalculationCodeReferenceUpdate();

            CalculationService service = CreateCalculationService(calculationsRepository: calculationsRepository,
                                                                  specificationRepository: specificationRepository,
                                                                  calculationVersionRepository: versionRepository,
                                                                  calculationCodeReferenceUpdate: calculationCodeReferenceUpdate);

            const string specificationId = "specId";
            const string calculationId   = "updatedCalc";

            Models.Specs.CalculationVersionComparisonModel comparison = new Models.Specs.CalculationVersionComparisonModel()
            {
                CalculationId = calculationId,
                Current       = new Models.Specs.Calculation
                {
                    Id              = "calcSpec1",
                    Name            = "Calculation to update",
                    CalculationType = Models.Specs.CalculationType.Funding,
                },
                Previous = new Models.Specs.Calculation
                {
                    Id              = "calcSpec1",
                    Name            = "Original Name",
                    CalculationType = Models.Specs.CalculationType.Funding,
                },
                SpecificationId = specificationId,
            };

            Reference user = new Reference("userId", "User Name");

            List <Calculation> calculations = new List <Calculation>()
            {
                new Calculation
                {
                    Id                       = calculationId,
                    Name                     = "Calculation to Update",
                    SpecificationId          = specificationId,
                    FundingPeriod            = new Reference("fp1", "Funding Period"),
                    CalculationSpecification = new Reference("calcSpec1", "Calculation to Update"),
                    CalculationType          = CalculationType.Funding,
                    Description              = "Calculation Description",
                    BuildProjectId           = "bpC1",
                    Policies                 = new List <Reference>(),
                    Current                  = new CalculationVersion
                    {
                        SourceCode    = "Return 10",
                        DecimalPlaces = 6,
                    }
                },
                new Calculation
                {
                    Id                       = "referenceCalc",
                    Name                     = "Calling Calculation To Update",
                    SpecificationId          = specificationId,
                    FundingPeriod            = new Reference("fp1", "Funding Period"),
                    CalculationSpecification = new Reference("calcSpec1", "Calculation to Update"),
                    CalculationType          = CalculationType.Funding,
                    Description              = "Calculation Description",
                    BuildProjectId           = "bpC1",
                    Policies                 = new List <Reference>(),
                    Current                  = new CalculationVersion
                    {
                        SourceCode    = "Return 50",
                        DecimalPlaces = 6,
                    }
                }
            };

            calculationsRepository
            .GetCalculationsBySpecificationId(Arg.Is(specificationId))
            .Returns(calculations);

            calculationsRepository
            .UpdateCalculation(Arg.Any <Calculation>())
            .Returns(HttpStatusCode.OK);

            calculationsRepository
            .GetCalculationById(Arg.Is(calculations[0].Id))
            .Returns(calculations[0]);

            Models.Specs.SpecificationSummary specification = new Models.Specs.SpecificationSummary()
            {
                Id   = specificationId,
                Name = "Specification Name",
            };

            specificationRepository
            .GetSpecificationSummaryById(Arg.Is(specificationId))
            .Returns(specification);

            CalculationVersion calculationVersion = new CalculationVersion
            {
                SourceCode = "Return CalculationToUpdate()",
                Version    = 2
            };

            versionRepository
            .CreateVersion(Arg.Any <CalculationVersion>(), Arg.Any <CalculationVersion>())
            .Returns(calculationVersion);

            // Act
            IEnumerable <Calculation> updatedCalculations = await service.UpdateCalculationCodeOnCalculationSpecificationChange(comparison, user);

            // Assert
            updatedCalculations
            .Should()
            .HaveCount(0);

            calculationCodeReferenceUpdate
            .Received(calculations.Count)
            .ReplaceSourceCodeReferences(Arg.Any <string>(),
                                         VisualBasicTypeGenerator.GenerateIdentifier(comparison.Previous.Name),
                                         VisualBasicTypeGenerator.GenerateIdentifier(comparison.Current.Name));

            foreach (Calculation calculation in calculations)
            {
                calculationCodeReferenceUpdate
                .Received(1)
                .ReplaceSourceCodeReferences(calculation.Current.SourceCode,
                                             VisualBasicTypeGenerator.GenerateIdentifier(comparison.Previous.Name),
                                             VisualBasicTypeGenerator.GenerateIdentifier(comparison.Current.Name));
            }
        }
        private void PopulateCachedCalculationAggregationsBatch(IEnumerable <ProviderResult> providerResults, Dictionary <string, List <decimal> > cachedCalculationAggregationsBatch, GenerateAllocationMessageProperties messageProperties)
        {
            if (cachedCalculationAggregationsBatch == null)
            {
                _logger.Error($"Cached calculation aggregations not found for key: {messageProperties.CalculationsAggregationsBatchCacheKey}");

                throw new Exception($"Cached calculation aggregations not found for key: {messageProperties.CalculationsAggregationsBatchCacheKey}");
            }

            IEnumerable <string> calculationsToAggregate = messageProperties.CalculationsToAggregate;

            foreach (ProviderResult providerResult in providerResults)
            {
                IEnumerable <CalculationResult> calculationResultsForAggregation = providerResult.CalculationResults.Where(m =>
                                                                                                                           calculationsToAggregate.Contains(VisualBasicTypeGenerator.GenerateIdentifier(m.Calculation.Name), StringComparer.InvariantCultureIgnoreCase));

                foreach (CalculationResult calculationResult in calculationResultsForAggregation)
                {
                    string calculationReferenceName = CalculationTypeGenerator.GenerateIdentifier(calculationResult.Calculation.Name.Trim());

                    string calcNameFromCalcsToAggregate = messageProperties.CalculationsToAggregate.FirstOrDefault(m => string.Equals(m, calculationReferenceName, StringComparison.InvariantCultureIgnoreCase));

                    if (!string.IsNullOrWhiteSpace(calcNameFromCalcsToAggregate) && cachedCalculationAggregationsBatch.ContainsKey(calculationReferenceName))
                    {
                        cachedCalculationAggregationsBatch[calcNameFromCalcsToAggregate].Add(calculationResult.Value.HasValue ? calculationResult.Value.Value : 0);
                    }
                }
            }
        }
Beispiel #6
0
        private async Task <IActionResult> GenerateAndCompile(BuildProject buildProject,
                                                              Calculation calculationToPreview,
                                                              IEnumerable <Calculation> calculations,
                                                              CompilerOptions compilerOptions,
                                                              PreviewRequest previewRequest)
        {
            Build compilerOutput = _sourceCodeService.Compile(buildProject, calculations, compilerOptions);

            compilerOutput = FilterDoubleToDecimalErrors(compilerOutput);

            await _sourceCodeService.SaveSourceFiles(compilerOutput.SourceFiles, buildProject.SpecificationId, SourceCodeType.Preview);

            if (compilerOutput.Success)
            {
                _logger.Information($"Build compiled successfully for calculation id {calculationToPreview.Id}");


                string calculationIdentifier = VisualBasicTypeGenerator.GenerateIdentifier(calculationToPreview.Name);

                IDictionary <string, string> functions = _sourceCodeService.GetCalculationFunctions(compilerOutput.SourceFiles);

                if (!functions.ContainsKey(calculationIdentifier))
                {
                    compilerOutput.Success = false;
                    compilerOutput.CompilerMessages.Add(new CompilerMessage {
                        Message = $"{calculationIdentifier} is not an aggregable field", Severity = Severity.Error
                    });
                }
                else
                {
                    if (previewRequest != null)
                    {
                        IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetCalculationAggregateFunctionParameters(previewRequest.SourceCode);

                        bool continueChecking = true;

                        if (!aggregateParameters.IsNullOrEmpty())
                        {
                            foreach (string aggregateParameter in aggregateParameters)
                            {
                                if (!functions.ContainsKey(aggregateParameter))
                                {
                                    compilerOutput.Success = false;
                                    compilerOutput.CompilerMessages.Add(new CompilerMessage {
                                        Message = $"{aggregateParameter} is not an aggregable field", Severity = Severity.Error
                                    });
                                    continueChecking = false;
                                    break;
                                }
                            }

                            if (continueChecking)
                            {
                                if (SourceCodeHelpers.IsCalcReferencedInAnAggregate(functions, calculationIdentifier))
                                {
                                    compilerOutput.Success = false;
                                    compilerOutput.CompilerMessages.Add(new CompilerMessage {
                                        Message = $"{calculationIdentifier} is already referenced in an aggregation that would cause nesting", Severity = Severity.Error
                                    });
                                }
                                else if (SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, previewRequest.SourceCode))
                                {
                                    compilerOutput.Success = false;
                                    compilerOutput.CompilerMessages.Add(new CompilerMessage {
                                        Message = $"{calculationIdentifier} cannot reference another calc that is being aggregated", Severity = Severity.Error
                                    });
                                }
                            }
                        }
                    }
                }
                //Forcing to compile for calc runs only
                compilerOptions.OptionStrictEnabled = false;

                Build nonPreviewCompilerOutput = _sourceCodeService.Compile(buildProject, calculations, compilerOptions);

                if (nonPreviewCompilerOutput.Success)
                {
                    await _sourceCodeService.SaveSourceFiles(nonPreviewCompilerOutput.SourceFiles, buildProject.SpecificationId, SourceCodeType.Release);
                }
            }
            else
            {
                _logger.Information($"Build did not compile successfully for calculation id {calculationToPreview.Id}");
            }

            CheckCircularReference(calculationToPreview, compilerOutput);

            LogMessages(compilerOutput, buildProject, calculationToPreview);

            return(new OkObjectResult(new PreviewResponse
            {
                Calculation = calculationToPreview,
                CompilerOutput = compilerOutput
            }));
        }