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)); }
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); } } }
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); } } } }
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 })); }