示例#1
0
        private async Task <Build> CheckDatasetValidAggregations(PreviewRequest previewRequest)
        {
            Build build = null;

            IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetDatasetAggregateFunctionParameters(previewRequest.SourceCode);

            if (aggregateParameters.IsNullOrEmpty())
            {
                return(build);
            }

            string cacheKey = $"{CacheKeys.DatasetRelationshipFieldsForSpecification}{previewRequest.SpecificationId}";

            IEnumerable <DatasetSchemaRelationshipModel> datasetSchemaRelationshipModels = Enumerable.Empty <DatasetSchemaRelationshipModel>();

            datasetSchemaRelationshipModels = await _cacheProvider.GetAsync <List <DatasetSchemaRelationshipModel> >(cacheKey);

            if (datasetSchemaRelationshipModels.IsNullOrEmpty())
            {
                ApiResponse <IEnumerable <Common.ApiClient.DataSets.Models.DatasetSchemaRelationshipModel> > datasetsApiClientResponse = await _datasetsApiClientPolicy.ExecuteAsync(() => _datasetsApiClient.GetDatasetSchemaRelationshipModelsForSpecificationId(previewRequest.SpecificationId));

                if (!datasetsApiClientResponse.StatusCode.IsSuccess())
                {
                    string message = $"No dataset schema relationship found for specificationId '{previewRequest.SpecificationId}'.";
                    _logger.Error(message);
                    throw new RetriableException(message);
                }

                if (datasetsApiClientResponse.Content != null)
                {
                    datasetSchemaRelationshipModels = _mapper.Map <IEnumerable <DatasetSchemaRelationshipModel> >(datasetsApiClientResponse.Content);
                }

                await _cacheProvider.SetAsync <List <DatasetSchemaRelationshipModel> >(cacheKey, datasetSchemaRelationshipModels.ToList());
            }

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

            IEnumerable <string> datasetAggregationFields = datasetSchemaRelationshipModels?.SelectMany(m => m.Fields?.Where(f => f.IsAggregable).Select(f => f.FullyQualifiedSourceName));

            foreach (string aggregateParameter in aggregateParameters)
            {
                if (datasetAggregationFields.IsNullOrEmpty() || !datasetAggregationFields.Any(m => string.Equals(m.Trim(), aggregateParameter.Trim(), StringComparison.CurrentCultureIgnoreCase)))
                {
                    compilerErrors.Add($"{aggregateParameter} is not an aggregable field");
                }
            }

            if (compilerErrors.Any())
            {
                build = new Build
                {
                    CompilerMessages = compilerErrors.Select(m => new CompilerMessage {
                        Message = m, Severity = Severity.Error
                    }).ToList()
                };
            }

            return(build);
        }
示例#2
0
        public void GetReferencedCalculations_GivenListOfCalcsAndSourceCodeContainsTwoCalcs_ReturnsTwoCalcs()
        {
            //Arrange
            string sourceCode = " calc1() + calc2()";

            IEnumerable <string> calcNames = new[] { "calc1", "calc2", "calc3" };

            //Act
            IEnumerable <string> results = SourceCodeHelpers.GetReferencedCalculations(calcNames, sourceCode);

            //Assert
            results
            .Count()
            .Should()
            .Be(2);

            results
            .ElementAt(0)
            .Should()
            .Be("calc1");

            results
            .ElementAt(1)
            .Should()
            .Be("calc2");
        }
示例#3
0
        public void GetCalculationAggregateFunctionParameters_GivenSourceHasZeroDatasetParameters_ReturnsEmptyList()
        {
            //Arrange
            string sourceCode = "Return Sum(Datasets.Testing1)";

            //Act
            IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetCalculationAggregateFunctionParameters(sourceCode);

            //Assert
            aggregateParameters
            .Should()
            .BeEmpty();
        }
示例#4
0
        public void CommentOutCode_GivenSourceCodeWithNoReasonForCommentingAndNoExceptionMessageWithHash_CommentsCode()
        {
            //Arrange
            string originalSourceCode = CreateSourceCode();

            string commentedSourceCode = CreateCommentedSourceCodeWithHash();

            //Act
            string result = SourceCodeHelpers.CommentOutCode(originalSourceCode, commentSymbol: "#");

            //Assert
            result
            .Should()
            .Be(commentedSourceCode);
        }
示例#5
0
        public void CodeContainsFullyQualifiedDatasetFieldIdentifier_GivenListOfIdentifiersAndSourceCodeDoesContainAnyFromListInsideAnAggregate_ReturnsTrue()
        {
            //Arrange
            string sourceCode = "return Sum(Datasets.Ds1.TestField1) + Max(Datasets.Ds1.TestField2)";

            IEnumerable <string> datasetFieldIdentifiers = new[] { "Datasets.Ds1.TestField2" };

            //Act
            bool result = SourceCodeHelpers.CodeContainsFullyQualifiedDatasetFieldIdentifier(sourceCode, datasetFieldIdentifiers);

            //Assert
            result
            .Should()
            .BeTrue();
        }
示例#6
0
        public void CodeContainsFullyQualifiedDatasetFieldIdentifier_GivenListOfIdentifiersAndSourceCodeDoesContainAnyFromListInAnyCase_ReturnsTrue()
        {
            //Arrange
            string sourceCode = "return Datasets.Ds1.TestField1 + Datasets.Ds1.TestField2";

            IEnumerable <string> datasetFieldIdentifiers = new[] { "DAtASetS.DS1.testfield2" };

            //Act
            bool result = SourceCodeHelpers.CodeContainsFullyQualifiedDatasetFieldIdentifier(sourceCode, datasetFieldIdentifiers);

            //Assert
            result
            .Should()
            .BeTrue();
        }
示例#7
0
        public void CodeContainsDatasetFieldIdentifier_GivenListOfIdentifiersAndSourceCodeDoesContainAnyFromListButWithExtraCharacters_ReturnsFalse1()
        {
            //Arrange
            string sourceCode = "return Datasets.Ds1.TestField1 + Datasets.Ds1.TestField2";

            IEnumerable <string> datasetFieldIdentifiers = new[] { "Datasets.Ds1.TestField23" };

            //Act
            bool result = SourceCodeHelpers.CodeContainsFullyQualifiedDatasetFieldIdentifier(sourceCode, datasetFieldIdentifiers);

            //Assert
            result
            .Should()
            .BeFalse();
        }
示例#8
0
        private async Task <Build> CheckDatasetValidAggregations(PreviewRequest previewRequest)
        {
            Build build = null;

            IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetDatasetAggregateFunctionParameters(previewRequest.SourceCode);

            if (aggregateParameters.IsNullOrEmpty())
            {
                return(build);
            }

            string cacheKey = $"{CacheKeys.DatasetRelationshipFieldsForSpecification}{previewRequest.SpecificationId}";

            IEnumerable <DatasetSchemaRelationshipModel> datasetSchemaRelationshipModels = Enumerable.Empty <DatasetSchemaRelationshipModel>();

            datasetSchemaRelationshipModels = await _cacheProvider.GetAsync <List <DatasetSchemaRelationshipModel> >(cacheKey);

            if (datasetSchemaRelationshipModels.IsNullOrEmpty())
            {
                datasetSchemaRelationshipModels = await _datasetRepository.GetDatasetSchemaRelationshipModelsForSpecificationId(previewRequest.SpecificationId);

                await _cacheProvider.SetAsync <List <DatasetSchemaRelationshipModel> >(cacheKey, datasetSchemaRelationshipModels.ToList());
            }

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

            IEnumerable <string> datasetAggregationFields = datasetSchemaRelationshipModels?.SelectMany(m => m.Fields?.Where(f => f.IsAggregable).Select(f => f.FullyQualifiedSourceName));

            foreach (string aggregateParameter in aggregateParameters)
            {
                if (datasetAggregationFields.IsNullOrEmpty() || !datasetAggregationFields.Any(m => string.Equals(m.Trim(), aggregateParameter.Trim(), StringComparison.CurrentCultureIgnoreCase)))
                {
                    compilerErrors.Add($"{aggregateParameter} is not an aggregable field");
                }
            }

            if (compilerErrors.Any())
            {
                build = new Build
                {
                    CompilerMessages = compilerErrors.Select(m => new CompilerMessage {
                        Message = m, Severity = Severity.Error
                    }).ToList()
                };
            }

            return(build);
        }
示例#9
0
        public void GetReferencedCalculations_GivenListOfCalcsAndSourceCodeDoesNotContainCalc_ReturnsEmptyList()
        {
            //Arrange
            string sourceCode = " calc4() + calc5()";

            IEnumerable <string> calcNames = new[] { "calc1", "calc2", "calc3" };

            //Act
            IEnumerable <string> results = SourceCodeHelpers.GetReferencedCalculations(calcNames, sourceCode);

            //Assert
            results
            .Any()
            .Should()
            .BeFalse();
        }
示例#10
0
        public void HasCalculationAggregateFunctionParameters_GivenSourceDoesNotContainAggregateParameterButCalcNameStartsWithAggregateFunction_ReturnsFalse()
        {
            //Arrange
            IEnumerable <string> sourceCodes = new[]
            {
                "Return SumTest()"
            };

            //Act
            bool result = SourceCodeHelpers.HasCalculationAggregateFunctionParameters(sourceCodes);

            //Assert
            result
            .Should()
            .BeFalse();
        }
示例#11
0
        public void HasCalculationAggregateFunctionParameters_GivenSourceDoesNotContainAggregateParameterButCalcNameContainsAggregateFunctionCaseInsensitive_ReturnsFalse()
        {
            //Arrange
            IEnumerable <string> sourceCodes = new[]
            {
                "Return TESTSUM()"
            };

            //Act
            bool result = SourceCodeHelpers.HasCalculationAggregateFunctionParameters(sourceCodes);

            //Assert
            result
            .Should()
            .BeFalse();
        }
示例#12
0
        public void GetReferencedCalculations_GivenAnEmptySourceCodeString_ReturnsEmptyList()
        {
            //Arrange
            string sourceCode = "";

            IEnumerable <string> calcNames = new[] { "calc1", "calc2", "calc3" };

            //Act
            IEnumerable <string> results = SourceCodeHelpers.GetReferencedCalculations(calcNames, sourceCode);

            //Assert
            results
            .Any()
            .Should()
            .BeFalse();
        }
示例#13
0
        public void CodeContainsFullyQualifiedDatasetFieldIdentifier_GivenListOfIdentifiersAndSourceCodeDoesContainAnyFromListAsGherkinWhithMultiSpaces_ReturnsTrue()
        {
            //Arrange
            string sourceCode = "Given the    dataset                    'ADULT Advanced Learner Loans Bursary Changed The Name Again'          field                  'Bursary_CurrentYear_PostPMPChange1' is equal to '101" + Environment.NewLine +
                                "Then the result for 'AB Calc 0804-001' is greater than '10'";

            IEnumerable <string> datasetFieldIdentifiers = new[] { "ADULT Advanced Learner Loans Bursary Changed The Name Again field Bursary_CurrentYear_PostPMPChange1" };

            //Act
            bool result = SourceCodeHelpers.CodeContainsFullyQualifiedDatasetFieldIdentifier(sourceCode.RemoveAllQuotes(), datasetFieldIdentifiers);

            //Assert
            result
            .Should()
            .BeTrue();
        }
示例#14
0
        public void HasCalculationAggregateFunctionParameters_GivenSourceDoesNotContainAggregateParameter_ReturnsFalse()
        {
            //Arrange
            IEnumerable <string> sourceCodes = new[]
            {
                "Return Sum(Datasets.Testing1",
                "Return Sum(Datasets.Testing2)"
            };

            //Act
            bool result = SourceCodeHelpers.HasCalculationAggregateFunctionParameters(sourceCodes);

            //Assert
            result
            .Should()
            .BeFalse();
        }
示例#15
0
        public void CommentOutCode_GivenSourceCodeAlreadyCommented_DoesntCommentCode()
        {
            //Arrange
            const string systemCommented = "'System Commented\r\n\r\n";

            string originalSourceCode = CreateCommentedSourceCode();

            string commentedSourceCode = CreateCommentedSourceCode();

            //Act
            string result = SourceCodeHelpers.CommentOutCode(originalSourceCode);

            //Assert
            result
            .Should()
            .Be($"{systemCommented}{commentedSourceCode}");
        }
示例#16
0
        public void CommentOutCode_GivenSourceCodeWithReasonForCommentingAndNoExceptionMessage_CommentsCode()
        {
            //Arrange
            string reason = "Just for test the unit tests";

            string originalSourceCode = CreateSourceCode();

            string commentedSourceCode = CreateCommentedSourceCode();

            //Act
            string result = SourceCodeHelpers.CommentOutCode(originalSourceCode, reason);

            //Assert
            result
            .Should()
            .Be($"'System Commented\r\n\r\n'{reason}\r\n\r\n\r\n{commentedSourceCode}");
        }
示例#17
0
        public void CommentOutCode_GivenSourceCodeWithNoReasonForCommentingAndNoExceptionMessage_CommentsCode()
        {
            //Arrange
            const string systemCommented = "'System Commented\r\n\r\n";

            string originalSourceCode = CreateSourceCode();

            string commentedSourceCode = CreateCommentedSourceCode();

            //Act
            string result = SourceCodeHelpers.CommentOutCode(originalSourceCode);

            //Assert
            result
            .Should()
            .Be($"{systemCommented}{commentedSourceCode}");
        }
示例#18
0
        public void HasCalculationAggregateFunctionParameters_GivenSourceContainsAggregateParameterCaseInsensitiveAndSpaces_ReturnsTrue()
        {
            //Arrange
            IEnumerable <string> sourceCodes = new[]
            {
                //"Return SuM    (Datasets.Testing1)",
                "Return mIn(Datasets.Testing1) + AVG(Calc1) + SuM   (Calc2)"
            };

            //Act
            bool result = SourceCodeHelpers.HasCalculationAggregateFunctionParameters(sourceCodes);

            //Assert
            result
            .Should()
            .BeTrue();
        }
示例#19
0
        public void GetCalculationAggregateFunctionParameters_GivenSourceHasOneCalculationParameter_ReturnsOneParameter()
        {
            //Arrange
            string sourceCode = "Return Sum(Datasets.Testing1) + Sum(Calc1)";

            //Act
            IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetCalculationAggregateFunctionParameters(sourceCode);

            //Assert
            aggregateParameters
            .Count()
            .Should()
            .Be(1);

            aggregateParameters
            .First()
            .Should()
            .Be("Calc1");
        }
示例#20
0
        public void IsCalcfReferencedInAnAggregate_GivenCalcIsNotReferencedInAnAggregate_ReturnsFalse()
        {
            //Arrange
            Dictionary <string, string> functions = new Dictionary <string, string>
            {
                { "Calc1", "Return Calc2()" },
                { "Calc2", "Return 1" },
                { "Calc3", "Return Sum(Calc2) + 5" },
                { "Calc4", "Return Sum(Calc1) + Avg(Calc3) + 5" }
            };

            string calcToCheck = "Calc4";

            //Act
            bool result = SourceCodeHelpers.IsCalcReferencedInAnAggregate(functions, calcToCheck);

            //Assert
            result
            .Should()
            .BeFalse();
        }
示例#21
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);
            }
        }
        public IEnumerable <CalculationRelationship> DetermineRelationshipsBetweenCalculations(IEnumerable <Calculation> calculations)
        {
            Guard.IsNotEmpty(calculations, nameof(calculations));

            Dictionary <string, string> calculationIdsBySourceCodeName = calculations
                                                                         .ToDictionary(_ => $"{_.Namespace}.{_.Current.SourceCodeName}", _ => _.Id);

            string[] calculationNames = calculations.Select(_ => $"{_.Namespace}.{_.Current.SourceCodeName}")
                                        .ToArray();

            return(calculations.SelectMany(_ =>
            {
                IEnumerable <string> relatedCalculationNames = SourceCodeHelpers.GetReferencedCalculations(calculationNames, _.Current.SourceCode);

                return relatedCalculationNames.Select(rel => new CalculationRelationship
                {
                    CalculationOneId = _.Id,
                    CalculationTwoId = calculationIdsBySourceCodeName.TryGetValue(rel, out string twoId) ?
                                       twoId :
                                       throw new InvalidOperationException($"Could not locate a calculation id for sourceCodeName {rel}")
                });
            }));
示例#23
0
        public void CommentOutCode_GivenSourceCodeWithReasonForCommentingAndExceptionMessage_CommentsCode()
        {
            //Arrange
            const string reason = "Just for test the unit tests";

            const string exceptionMessage = "Exception thrown, just for test the unit tests";

            const string exceptionType = "DatasetReferenceChangeException";

            string originalSourceCode = CreateSourceCode();

            string commentedSourceCode = CreateCommentedSourceCode();

            string expected = $"'System Commented\r\n\r\n'{reason}\r\n\r\n\r\nThrow New {exceptionType}(\"{exceptionMessage}\")\r\n\r\n\r\n{commentedSourceCode}";

            //Act
            string result = SourceCodeHelpers.CommentOutCode(originalSourceCode, reason, exceptionMessage, exceptionType);

            //Assert
            result
            .Should()
            .Be(expected);
        }
示例#24
0
        public void CheckSourceForExistingCalculationAggregates_GivenCalcIsReferencedInAnAggregateWithDeepNestingAndFurtherLevelsAndIncludeWhitespaceInAggregateCall_ReturnsTrue()
        {
            //Arrange
            Dictionary <string, string> functions = new Dictionary <string, string>
            {
                { "Calc1", "Return Calc2() + Min( Calc6)" },
                { "Calc2", "Return 1" },
                { "Calc3", "Return Sum(Calc2) + 5" },
                { "Calc4", "Return Sum(Calc2) + Avg(Calc2) + Min( Calc3)" },
                { "Calc5", "Return Calc1()" },
                { "Calc6", "Return Calc5()" },
                { "Calc7", "Return Calc6()" }
            };

            string calcToCheck = "Return Sum(Calc2) + Avg(Calc2) + Sum(Calc3)";

            //Act
            bool result = SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, calcToCheck);

            //Assert
            result
            .Should()
            .BeTrue();
        }
示例#25
0
        public void CheckSourceForExistingCalculationAggregates_GivenCalcContainsCalcReferenceThatIncludesAvgInTheNameButNoOtherAggregateRefrences_ReturnsFalse()
        {
            //Arrange
            Dictionary <string, string> functions = new Dictionary <string, string>
            {
                { "Calc1", "Return Calc2() + Min( Calc6)" },
                { "Calc2", "Return 1" },
                { "Calc3", "Return Sum(Calc2) + 5" },
                { "Calc4", "Return Sum(Calc2) + Avg(Calc2) + Min( Calc3)" },
                { "Calc5", "Return CalcAvg1()" },
                { "Calc6", "Return Calc5()" },
                { "Calc7", "Return Calc6()" }
            };

            string calcToCheck = "Return CalcAvg1() = Calc6()";

            //Act
            bool result = SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, calcToCheck);

            //Assert
            result
            .Should()
            .BeFalse();
        }
示例#26
0
        public void GetCalculationAggregateFunctionParameters_GivenSourceHasTwoCalculationParameters_ReturnsTwoParameters()
        {
            //Arrange
            string sourceCode = "Return Sum(Datasets.Testing1) + Avg(Calc1) + Sum(Calc2)";

            //Act
            IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetCalculationAggregateFunctionParameters(sourceCode);

            //Assert
            aggregateParameters
            .Count()
            .Should()
            .Be(2);

            aggregateParameters
            .ElementAt(0)
            .Should()
            .Be("Calc1");

            aggregateParameters
            .ElementAt(1)
            .Should()
            .Be("Calc2");
        }
        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);
                }
            }
        }
示例#28
0
        private async Task <IActionResult> GenerateAndCompile(BuildProject buildProject,
                                                              Calculation calculationToPreview,
                                                              IEnumerable <Calculation> calculations,
                                                              CompilerOptions compilerOptions,
                                                              PreviewRequest previewRequest)
        {
            PreviewProviderCalculationResponseModel previewProviderCalculation = null;

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

            if (compilerOutput.SourceFiles != null)
            {
                await _sourceCodeService.SaveSourceFiles(compilerOutput.SourceFiles, buildProject.SpecificationId, SourceCodeType.Preview);
            }

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

                string calculationIdentifier = $"{_typeIdentifierGenerator.GenerateIdentifier(calculationToPreview.Namespace)}.{_typeIdentifierGenerator.GenerateIdentifier(calculationToPreview.Name)}";

                IDictionary <string, string> functions = _sourceCodeService.GetCalculationFunctions(compilerOutput.SourceFiles);
                IDictionary <string, string> calculationIdentifierMap = calculations
                                                                        .Select(_ => new
                {
                    Identifier = $"{_typeIdentifierGenerator.GenerateIdentifier(_.Namespace)}.{_typeIdentifierGenerator.GenerateIdentifier(_.Name)}",
                    CalcName   = _.Name
                })
                                                                        .ToDictionary(d => d.Identifier, d => d.CalcName);

                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)
                    {
                        if (!SourceCodeHelpers.HasReturn(previewRequest.SourceCode))
                        {
                            compilerOutput.Success = false;
                            compilerOutput.CompilerMessages.Add(new CompilerMessage {
                                Message = $"{calculationIdentifier} must have a return statement so that a calculation result will be returned", Severity = Severity.Error
                            });
                        }
                        else
                        {
                            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 (calculationIdentifierMap.ContainsKey(aggregateParameter))
                                    {
                                        Calculation calculation = calculations.SingleOrDefault(_ => _.Name == calculationIdentifierMap[aggregateParameter]);

                                        if (calculation.Current.DataType != CalculationDataType.Decimal)
                                        {
                                            compilerOutput.Success = false;
                                            compilerOutput.CompilerMessages.Add(new CompilerMessage
                                            {
                                                Message =
                                                    $"Only decimal fields can be used on aggregation. {aggregateParameter} has data type of {calculation.Current.DataType}",
                                                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
                                        });
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                _logger.Information($"Build did not compile successfully for calculation id {calculationToPreview.Id}");
            }

            LogMessages(compilerOutput, buildProject, calculationToPreview);

            if (!string.IsNullOrEmpty(previewRequest.ProviderId))
            {
                CalculationSummaryModel calculationSummaryModel = calculationToPreview.ToSummaryModel();
                CalcEngineModels.CalculationSummaryModel model  = _mapper.Map <CalcEngineModels.CalculationSummaryModel>(calculationSummaryModel);

                CalcEngineModels.PreviewCalculationRequest previewCalculationRequest = new CalcEngineModels.PreviewCalculationRequest
                {
                    AssemblyContent = compilerOutput.Assembly,
                    PreviewCalculationSummaryModel = model
                };

                ApiResponse <CalcEngineProviderResult> previewCalcResultApiResponse =
                    await _calcEngineApiClientPolicy.ExecuteAsync(
                        () => _calcEngineApiClient.PreviewCalculationResults(
                            previewRequest.SpecificationId,
                            previewRequest.ProviderId,
                            previewCalculationRequest));

                if (previewCalcResultApiResponse.StatusCode.IsSuccess())
                {
                    CalcEngineProviderResult calcEngineProviderResult = previewCalcResultApiResponse.Content;

                    previewProviderCalculation = new PreviewProviderCalculationResponseModel
                    {
                        ProviderName      = calcEngineProviderResult.Provider.Name,
                        CalculationResult = _mapper.Map <CalculationResult>(
                            calcEngineProviderResult.CalculationResults.SingleOrDefault(_ => _.Calculation?.Id == calculationToPreview.Id)),
                    };
                }
            }

            return(new OkObjectResult(new PreviewResponse
            {
                Calculation = calculationToPreview.ToResponseModel(),
                CompilerOutput = compilerOutput,
                PreviewProviderCalculation = previewProviderCalculation
            }));
        }
示例#29
0
        public async Task <IActionResult> SaveVersion(HttpRequest request)
        {
            string json = await request.GetRawBodyStringAsync();

            CreateNewTestScenarioVersion scenarioVersion = JsonConvert.DeserializeObject <CreateNewTestScenarioVersion>(json);

            if (scenarioVersion == null)
            {
                _logger.Error("A null scenario version was provided");

                return(new BadRequestObjectResult("Null or empty calculation Id provided"));
            }

            BadRequestObjectResult validationResult = (await _createNewTestScenarioVersionValidator.ValidateAsync(scenarioVersion)).PopulateModelState();

            if (validationResult != null)
            {
                return(validationResult);
            }

            TestScenario testScenario = null;

            if (!string.IsNullOrEmpty(scenarioVersion.Id))
            {
                testScenario = await _scenariosRepository.GetTestScenarioById(scenarioVersion.Id);
            }

            bool saveAsVersion = true;

            SpecificationSummary specification = await _specificationsRepository.GetSpecificationSummaryById(scenarioVersion.SpecificationId);

            if (specification == null)
            {
                _logger.Error($"Unable to find a specification for specification id : {scenarioVersion.SpecificationId}");

                return(new StatusCodeResult(412));
            }

            Reference user = request.GetUserOrDefault();

            if (testScenario == null)
            {
                string Id = Guid.NewGuid().ToString();

                testScenario = new TestScenario
                {
                    Id = Id,
                    SpecificationId = specification.Id,
                    Name            = scenarioVersion.Name,
                    Current         = new TestScenarioVersion
                    {
                        Date             = DateTimeOffset.Now.ToLocalTime(),
                        TestScenarioId   = Id,
                        PublishStatus    = PublishStatus.Draft,
                        Version          = 1,
                        Author           = user,
                        Gherkin          = scenarioVersion.Scenario,
                        Description      = scenarioVersion.Description,
                        FundingPeriodId  = specification.FundingPeriod.Id,
                        FundingStreamIds = specification.FundingStreams.Select(s => s.Id).ToArraySafe(),
                    }
                };
            }
            else
            {
                testScenario.Name = scenarioVersion.Name;

                saveAsVersion = !string.Equals(scenarioVersion.Scenario, testScenario.Current.Gherkin) ||
                                scenarioVersion.Description != testScenario.Current.Description;

                TestScenarioVersion newVersion = testScenario.Current.Clone() as TestScenarioVersion;

                if (saveAsVersion == true)
                {
                    newVersion.Author           = user;
                    newVersion.Gherkin          = scenarioVersion.Scenario;
                    newVersion.Description      = scenarioVersion.Description;
                    newVersion.FundingStreamIds = specification.FundingStreams.Select(s => s.Id).ToArraySafe();
                    newVersion.FundingPeriodId  = specification.FundingPeriod.Id;

                    newVersion = await _versionRepository.CreateVersion(newVersion, testScenario.Current);

                    testScenario.Current = newVersion;
                }
            }

            HttpStatusCode statusCode = await _scenariosRepository.SaveTestScenario(testScenario);

            if (!statusCode.IsSuccess())
            {
                _logger.Error($"Failed to save test scenario with status code: {statusCode.ToString()}");

                return(new StatusCodeResult((int)statusCode));
            }

            await _versionRepository.SaveVersion(testScenario.Current);

            ScenarioIndex scenarioIndex = CreateScenarioIndexFromScenario(testScenario, specification);

            await _searchRepository.Index(new List <ScenarioIndex> {
                scenarioIndex
            });

            await _cacheProvider.RemoveAsync <List <TestScenario> >($"{CacheKeys.TestScenarios}{testScenario.SpecificationId}");

            await _cacheProvider.RemoveAsync <GherkinParseResult>($"{CacheKeys.GherkinParseResult}{testScenario.Id}");

            IEnumerable <Models.Calcs.CalculationCurrentVersion> calculations = await _calcsRepositoryPolicy.ExecuteAsync(() => _calcsRepository.GetCurrentCalculationsBySpecificationId(specification.Id));

            if (calculations.IsNullOrEmpty())
            {
                _logger.Information($"No calculations found to test for specification id: '{specification.Id}'");
            }
            else
            {
                string correlationId = request.GetCorrelationId();

                try
                {
                    Trigger trigger = new Trigger
                    {
                        EntityId   = testScenario.Id,
                        EntityType = nameof(TestScenario),
                        Message    = $"Saving test scenario: '{testScenario.Id}'"
                    };

                    bool generateCalculationAggregations = SourceCodeHelpers.HasCalculationAggregateFunctionParameters(calculations.Select(m => m.SourceCode));

                    Job job = await SendInstructAllocationsToJobService(specification.Id, user, trigger, correlationId, generateCalculationAggregations);

                    _logger.Information($"New job of type '{job.JobDefinitionId}' created with id: '{job.Id}'");
                }
                catch (Exception ex)
                {
                    _logger.Error(ex, $"Failed to create job of type '{JobConstants.DefinitionNames.CreateInstructAllocationJob}' on specification '{specification.Id}'");

                    return(new InternalServerErrorResult($"An error occurred attempting to execute calculations prior to running tests on specification '{specification.Id}'"));
                }
            }

            CurrentTestScenario testScenarioResult = await _scenariosRepository.GetCurrentTestScenarioById(testScenario.Id);

            return(new OkObjectResult(testScenarioResult));
        }
示例#30
0
        public async Task ProcessDataset(Message message)
        {
            Guard.ArgumentNotNull(message, nameof(message));

            IDictionary <string, object> properties = message.UserProperties;

            Dataset dataset = message.GetPayloadAsInstanceOf <Dataset>();

            string jobId = message.UserProperties["jobId"].ToString();

            await UpdateJobStatus(jobId, null, 0);

            if (dataset == null)
            {
                _logger.Error("A null dataset was provided to ProcessData");
                await UpdateJobStatus(jobId, false, 100, "Failed to Process - null dataset provided");

                return;
            }

            if (!message.UserProperties.ContainsKey("specification-id"))
            {
                _logger.Error("Specification Id key is missing in ProcessDataset message properties");
                await UpdateJobStatus(jobId, false, 100, "Failed to Process - specification id not provided");

                return;
            }

            string specificationId = message.UserProperties["specification-id"].ToString();

            if (string.IsNullOrWhiteSpace(specificationId))
            {
                _logger.Error("A null or empty specification id was provided to ProcessData");
                await UpdateJobStatus(jobId, false, 100, "Failed to Process - specification if is null or empty");

                return;
            }

            if (!message.UserProperties.ContainsKey("relationship-id"))
            {
                _logger.Error("Relationship Id key is missing in ProcessDataset message properties");
                await UpdateJobStatus(jobId, false, 100, "Failed to Process - relationship id not provided");

                return;
            }

            string relationshipId = message.UserProperties["relationship-id"].ToString();

            if (string.IsNullOrWhiteSpace(relationshipId))
            {
                _logger.Error("A null or empty relationship id was provided to ProcessDataset");
                await UpdateJobStatus(jobId, false, 100, "Failed to Process - relationship id is null or empty");

                return;
            }

            DefinitionSpecificationRelationship relationship = await _datasetRepository.GetDefinitionSpecificationRelationshipById(relationshipId);

            if (relationship == null)
            {
                _logger.Error($"Relationship not found for relationship id: {relationshipId}");
                await UpdateJobStatus(jobId, false, 100, "Failed to Process - relationship not found");

                return;
            }

            BuildProject buildProject = null;

            Reference user = message.GetUserDetails();

            try
            {
                buildProject = await ProcessDataset(dataset, specificationId, relationshipId, relationship.DatasetVersion.Version, user);

                await UpdateJobStatus(jobId, true, 100, "Processed Dataset");
            }
            catch (NonRetriableException argEx)
            {
                // This type of exception is not retriable so fail
                _logger.Error(argEx, $"Failed to run ProcessDataset with exception: {argEx.Message} for relationship ID '{relationshipId}'");
                await UpdateJobStatus(jobId, false, 100, $"Failed to run Process - {argEx.Message}");

                return;
            }
            catch (Exception exception)
            {
                // Unknown exception occurred so allow it to be retried
                _logger.Error(exception, $"Failed to run ProcessDataset with exception: {exception.Message} for relationship ID '{relationshipId}'");
                throw;
            }

            if (buildProject != null && !buildProject.DatasetRelationships.IsNullOrEmpty() && buildProject.DatasetRelationships.Any(m => m.DefinesScope))
            {
                string userId   = user != null ? user.Id : "";
                string userName = user != null ? user.Name : "";

                try
                {
                    HttpStatusCode statusCode = await _calcsRepository.CompileAndSaveAssembly(specificationId);

                    if (!statusCode.IsSuccess())
                    {
                        string errorMessage = $"Failed to compile and save assembly for specification id '{specificationId}' with status code '{statusCode}'";

                        _logger.Error(errorMessage);

                        throw new NonRetriableException(errorMessage);
                    }

                    Trigger trigger = new Trigger
                    {
                        EntityId   = relationshipId,
                        EntityType = nameof(DefinitionSpecificationRelationship),
                        Message    = $"Processed dataset relationship: '{relationshipId}' for specification: '{specificationId}'"
                    };
                    string correlationId = message.GetCorrelationId();

                    IEnumerable <CalculationCurrentVersion> allCalculations = await _calcsRepository.GetCurrentCalculationsBySpecificationId(specificationId);

                    bool generateCalculationAggregations = allCalculations.IsNullOrEmpty() ? false : SourceCodeHelpers.HasCalculationAggregateFunctionParameters(allCalculations.Select(m => m.SourceCode));

                    await SendInstructAllocationsToJobService($"{CacheKeys.ScopedProviderSummariesPrefix}{specificationId}", specificationId, userId, userName, trigger, correlationId, generateCalculationAggregations);
                }
                catch (Exception ex)
                {
                    _logger.Error(ex, $"Failed to create job of type '{JobConstants.DefinitionNames.CreateInstructAllocationJob}' on specification '{specificationId}'");

                    throw new Exception($"Failed to create job of type '{JobConstants.DefinitionNames.CreateInstructAllocationJob}' on specification '{specificationId}'", ex);
                }
            }
        }