diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 2a49386..1ffab51 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -49,8 +49,8 @@ jobs: "@ | Out-File -Encoding ASCII plugin.nuspec - dotnet publish Plugin/windows/windows.csproj -c Release -o p/windows - dotnet publish Plugin/main/main.csproj -c Release -o p/main + dotnet publish -p:DebugType=embedded -p:GenerateDocumentation=false Plugin/windows/windows.csproj -c Release -o p/windows + dotnet publish -p:DebugType=embedded -p:GenerateDocumentation=false Plugin/main/main.csproj -c Release -o p/main cmd /c 7z a -tzip HIC.Extensions.nupkg plugin.nuspec p dotnet run --project RDMP/Tools/rdmp/rdmp.csproj -c Release -- pack -p --file HIC.Extensions.nupkg --dir yaml dotnet run --project RDMP/Tools/rdmp/rdmp.csproj -c Release -- cmd listsupportedcommands --dir yaml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a664f89 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "RDMP"] + path = RDMP + url = https://github.com/HicServices/RDMP.git + branch = feature/rc4 diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/AutomationUserInterface.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/AutomationUserInterface.cs index 7972831..06dd2cb 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/AutomationUserInterface.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/AutomationUserInterface.cs @@ -25,7 +25,6 @@ public class AutomationUserInterface : PluginUserInterface public AutomationUserInterface(IBasicActivateItems itemActivator) : base(itemActivator) { - _overlayProvider = new IconOverlayProvider(); try { _scheduleIcon = Image.Load(AutomationImages.AutomateExtractionSchedule); @@ -43,12 +42,12 @@ public override Image GetImage(object concept, OverlayKind kind = Overla { if (concept is AutomateExtractionSchedule || concept as Type == typeof(AutomateExtractionSchedule)) { - return _overlayProvider.GetOverlay(_scheduleIcon,kind); + return IconOverlayProvider.GetOverlay(_scheduleIcon,kind); } if (concept is AutomateExtraction || concept as Type == typeof(AutomateExtraction)) { - return _overlayProvider.GetOverlay(_automateExtractionIcon, kind); + return IconOverlayProvider.GetOverlay(_automateExtractionIcon, kind); } return base.GetImage(concept, kind); @@ -56,19 +55,23 @@ public override Image GetImage(object concept, OverlayKind kind = Overla public override object[] GetChildren(object model) { - if (model is IProject p) + switch (model) { - var schedule = GetScheduleIfAny(p); + case IProject p: + { + var schedule = GetScheduleIfAny(p); - if(schedule != null) - return new[] { schedule }; - } - - if(model is IExtractionConfiguration ec) - { - var automate = GetAutomateExtractionIfAny(ec); - if (automate != null) - return new[] { automate }; + if(schedule != null) + return new[] { schedule }; + break; + } + case IExtractionConfiguration ec: + { + var automate = GetAutomateExtractionIfAny(ec); + if (automate != null) + return new[] { automate }; + break; + } } return base.GetChildren(model); @@ -78,35 +81,24 @@ private AutomateExtractionSchedule GetScheduleIfAny(IProject p) { TryGettingAutomationRepository(); - if (AutomationRepository == null) - { - return null; - } - - return AllSchedules.FirstOrDefault(aes => aes.Project_ID == p.ID); + return AutomationRepository == null ? null : AllSchedules.FirstOrDefault(aes => aes.Project_ID == p.ID); } private AutomateExtraction GetAutomateExtractionIfAny(IExtractionConfiguration ec) { TryGettingAutomationRepository(); - if (AutomationRepository == null) - { - return null; - } - - return AllAutomateExtractions.FirstOrDefault(ae => ae.ExtractionConfiguration_ID == ec.ID); + return AutomationRepository == null ? null : AllAutomateExtractions.FirstOrDefault(ae => ae.ExtractionConfigurationId == ec.ID); } - DateTime lastLook = DateTime.MinValue; - private IconOverlayProvider _overlayProvider; - private Image _scheduleIcon; - private Image _automateExtractionIcon; + DateTime _lastLook = DateTime.MinValue; + private readonly Image _scheduleIcon; + private readonly Image _automateExtractionIcon; private void TryGettingAutomationRepository() { - // we looked recently already dont spam that thing - if (DateTime.Now - lastLook < TimeSpan.FromSeconds(5)) + // we looked recently already don't spam that thing + if (DateTime.Now - _lastLook < TimeSpan.FromSeconds(5)) return; if (AutomationRepository != null) @@ -120,39 +112,34 @@ private void TryGettingAutomationRepository() AllAutomateExtractions = AutomationRepository.GetAllObjects(); AllSchedules = AutomationRepository.GetAllObjects(); - lastLook = DateTime.Now; + _lastLook = DateTime.Now; } catch (Exception) { AutomationRepository = null; - lastLook = DateTime.Now; + _lastLook = DateTime.Now; } } public override IEnumerable GetAdditionalRightClickMenuItems(object o) { - if (o is AllExternalServersNode) - { - yield return new ExecuteCommandCreateNewExternalDatabaseServer(BasicActivator, new AutomateExtractionPluginPatcher(), PermissableDefaults.None); - } - - - if(o is IProject p) + switch (o) { - yield return new ExecuteCommandCreateNewAutomateExtractionSchedule(BasicActivator, p); - } - if(o is IExtractionConfiguration ec) - { - yield return new ExecuteCommandCreateNewAutomateExtraction(BasicActivator, ec); - } - - if(o is AutomateExtraction ae) - { - yield return new ExecuteCommandSet(BasicActivator, ae, typeof(AutomateExtraction).GetProperty(nameof(AutomateExtraction.BaselineDate))) - { - OverrideCommandName = "Set Baseline Date" - }; + case AllExternalServersNode: + yield return new ExecuteCommandCreateNewExternalDatabaseServer(BasicActivator, new AutomateExtractionPluginPatcher(), PermissableDefaults.None); + break; + case IProject p: + yield return new ExecuteCommandCreateNewAutomateExtractionSchedule(BasicActivator, p); + break; + case IExtractionConfiguration ec: + yield return new ExecuteCommandCreateNewAutomateExtraction(BasicActivator, ec); + break; + case AutomateExtraction ae: + yield return new ExecuteCommandSet(BasicActivator, ae, typeof(AutomateExtraction).GetProperty(nameof(AutomateExtraction.BaselineDate))) + { + OverrideCommandName = "Set Baseline Date" + }; + break; } - } } \ No newline at end of file diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/BasicAutomationCommandExecution.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/BasicAutomationCommandExecution.cs index 79d3063..6f33cff 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/BasicAutomationCommandExecution.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/BasicAutomationCommandExecution.cs @@ -17,14 +17,13 @@ public BasicAutomationCommandExecution(IBasicActivateItems activator):base(activ } catch (System.Exception e) { - SetImpossible("No Automation Repository Found:" + e.Message); + SetImpossible($"No Automation Repository Found:{e.Message}"); return; } if (AutomationRepository == null) { SetImpossible("There is no Automation Repository configured"); - return; } } diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtraction.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtraction.cs index 5e7ce35..c3e05c1 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtraction.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtraction.cs @@ -12,50 +12,50 @@ namespace LoadModules.Extensions.AutomationPlugins.Data; -public class AutomateExtraction : DatabaseEntity, IMapsDirectlyToDatabaseTable +public class AutomateExtraction : DatabaseEntity { private readonly AutomateExtractionRepository _repository; #region Database Properties - private int _extractionConfiguration_ID; - private int _automateExtractionSchedule_ID; + private int _extractionConfigurationId; + private int _automateExtractionScheduleId; private bool _disabled; private DateTime? _baselineDate; private bool _refreshCohort; private bool _release; - public int ExtractionConfiguration_ID + public int ExtractionConfigurationId { - get { return _extractionConfiguration_ID; } - set { SetField(ref _extractionConfiguration_ID, value); } + get => _extractionConfigurationId; + set => SetField(ref _extractionConfigurationId, value); } - public int AutomateExtractionSchedule_ID + public int AutomateExtractionScheduleId { - get { return _automateExtractionSchedule_ID; } - set { SetField(ref _automateExtractionSchedule_ID, value); } + get => _automateExtractionScheduleId; + set => SetField(ref _automateExtractionScheduleId, value); } public bool Disabled { - get { return _disabled; } - set { SetField(ref _disabled, value); } + get => _disabled; + set => SetField(ref _disabled, value); } public DateTime? BaselineDate { - get { return _baselineDate; } - set { SetField(ref _baselineDate, value); } + get => _baselineDate; + set => SetField(ref _baselineDate, value); } public bool RefreshCohort { - get { return _refreshCohort; } - set {SetField(ref _refreshCohort , value); } + get => _refreshCohort; + set => SetField(ref _refreshCohort , value); } public bool Release { - get { return _release; } - set { SetField(ref _release , value);} + get => _release; + set => SetField(ref _release , value); } #endregion @@ -63,28 +63,22 @@ public bool Release #region Relationships [NoMappingToDatabase] - public IExtractionConfiguration ExtractionConfiguration { get - { - return _repository.DataExportRepository.GetObjectByID(ExtractionConfiguration_ID); - } } + public IExtractionConfiguration ExtractionConfiguration => _repository.DataExportRepository.GetObjectByID(ExtractionConfigurationId); [NoMappingToDatabase] - public AutomateExtractionSchedule AutomateExtractionSchedule { get - { - return _repository.GetObjectByID(AutomateExtractionSchedule_ID); - }} + public AutomateExtractionSchedule AutomateExtractionSchedule => _repository.GetObjectByID(AutomateExtractionScheduleId); #endregion public AutomateExtraction(PluginRepository repository, AutomateExtractionSchedule schedule, IExtractionConfiguration config) { _repository = (AutomateExtractionRepository) repository; - repository.InsertAndHydrate(this, new Dictionary() + repository.InsertAndHydrate(this, new Dictionary { {"AutomateExtractionSchedule_ID",schedule.ID}, {"ExtractionConfiguration_ID",config.ID}, {"RefreshCohort",false}, - {"Release",false}, + {"Release",false} }); @@ -95,8 +89,8 @@ public AutomateExtraction(PluginRepository repository, DbDataReader r) : base(repository, r) { _repository = (AutomateExtractionRepository) repository; - ExtractionConfiguration_ID = Convert.ToInt32(r["ExtractionConfiguration_ID"]); - AutomateExtractionSchedule_ID = Convert.ToInt32(r["AutomateExtractionSchedule_ID"]); + ExtractionConfigurationId = Convert.ToInt32(r["ExtractionConfiguration_ID"]); + AutomateExtractionScheduleId = Convert.ToInt32(r["AutomateExtractionSchedule_ID"]); Disabled = Convert.ToBoolean(r["Disabled"]); BaselineDate = ObjectToNullableDateTime(r["BaselineDate"]); @@ -110,7 +104,7 @@ public AutomateExtraction(PluginRepository repository, DbDataReader r) public override string ToString() { _cachedExtractionConfiguration ??= - _repository.DataExportRepository.GetObjectByID(ExtractionConfiguration_ID); + _repository.DataExportRepository.GetObjectByID(ExtractionConfigurationId); return _cachedExtractionConfiguration.Name; } @@ -118,24 +112,25 @@ public override string ToString() public DataTable GetIdentifiersTable() { var dt = new DataTable(); + dt.BeginLoadData(); var repo = (TableRepository)Repository; var server = repo.DiscoveredServer; - using (var con = server.GetConnection()) - { - con.Open(); - var cmd = server.GetCommand("Select ReleaseID from ReleaseIdentifiersSeen", con); - var da = server.GetDataAdapter(cmd); - da.Fill(dt); - } + using var con = server.GetConnection(); + con.Open(); + var cmd = server.GetCommand("Select ReleaseID from ReleaseIdentifiersSeen", con); + var da = server.GetDataAdapter(cmd); + da.Fill(dt); + dt.EndLoadData(); return dt; } public SuccessfullyExtractedResults GetSuccessIfAnyFor(IExtractableDataSet ds) { - return _repository.GetAllObjects(@"WHERE ExtractableDataSet_ID = " + ds.ID + " AND AutomateExtraction_ID = " + ID).SingleOrDefault(); + return _repository.GetAllObjects( + $@"WHERE ExtractableDataSet_ID = {ds.ID} AND AutomateExtraction_ID = {ID}").SingleOrDefault(); } public void ClearBaselines() @@ -143,13 +138,13 @@ public void ClearBaselines() using (var con = _repository.DiscoveredServer.GetConnection()) { con.Open(); - new SqlCommand(@"Delete From + new SqlCommand($@"Delete From [ReleaseIdentifiersSeen] where - AutomateExtraction_ID = " + ID, (SqlConnection) con).ExecuteNonQuery(); + AutomateExtraction_ID = {ID}", (SqlConnection) con).ExecuteNonQuery(); } - foreach (SuccessfullyExtractedResults r in _repository.GetAllObjectsWithParent(this)) + foreach (var r in _repository.GetAllObjectsWithParent(this)) r.DeleteInDatabase(); BaselineDate = null; diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtractionSchedule.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtractionSchedule.cs index 5c5a3cf..909461e 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtractionSchedule.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/AutomateExtractionSchedule.cs @@ -33,60 +33,60 @@ public class AutomateExtractionSchedule : DatabaseEntity, INamed public AutomationTimeScale ExecutionTimescale { - get { return _executionTimescale; } - set { SetField(ref _executionTimescale, value); } + get => _executionTimescale; + set => SetField(ref _executionTimescale, value); } public string UserRequestingRefresh { - get { return _userRequestingRefresh; } - set { SetField(ref _userRequestingRefresh, value); } + get => _userRequestingRefresh; + set => SetField(ref _userRequestingRefresh, value); } public DateTime? UserRequestingRefreshDate { - get { return _userRequestingRefreshDate; } - set { SetField(ref _userRequestingRefreshDate, value); } + get => _userRequestingRefreshDate; + set => SetField(ref _userRequestingRefreshDate, value); } public string Ticket { - get { return _ticket; } - set { SetField(ref _ticket, value); } + get => _ticket; + set => SetField(ref _ticket, value); } public string Name { - get { return _name; } - set { SetField(ref _name, value); } + get => _name; + set => SetField(ref _name, value); } public string Comment { - get { return _comment; } - set { SetField(ref _comment, value); } + get => _comment; + set => SetField(ref _comment, value); } public bool Disabled { - get { return _disabled; } - set { SetField(ref _disabled, value); } + get => _disabled; + set => SetField(ref _disabled, value); } public int Project_ID { - get { return _project_ID; } - set { SetField(ref _project_ID, value); } + get => _project_ID; + set => SetField(ref _project_ID, value); } public int? Pipeline_ID { - get { return _pipeline_ID; } - set { SetField(ref _pipeline_ID, value); } + get => _pipeline_ID; + set => SetField(ref _pipeline_ID, value); } public TimeSpan ExecutionTimeOfDay { - get { return _executionTimeOfDay; } - set { SetField(ref _executionTimeOfDay , value); } + get => _executionTimeOfDay; + set => SetField(ref _executionTimeOfDay , value); } public int? ReleasePipeline_ID { - get { return _releasePipelineId; } - set { SetField(ref _releasePipelineId , value); } + get => _releasePipelineId; + set => SetField(ref _releasePipelineId , value); } #endregion @@ -94,44 +94,26 @@ public int? ReleasePipeline_ID #region Database Relationships [NoMappingToDatabase] - public IPipeline Pipeline { get - { - return Pipeline_ID != null ? _repository.CatalogueRepository.GetObjectByID(Pipeline_ID.Value) : null; - } } + public IPipeline Pipeline => Pipeline_ID != null ? _repository.CatalogueRepository.GetObjectByID(Pipeline_ID.Value) : null; [NoMappingToDatabase] - public IPipeline ReleasePipeline - { - get - { - return ReleasePipeline_ID != null ? _repository.CatalogueRepository.GetObjectByID(ReleasePipeline_ID.Value) : null; - } - } + public IPipeline ReleasePipeline => ReleasePipeline_ID != null ? _repository.CatalogueRepository.GetObjectByID(ReleasePipeline_ID.Value) : null; [NoMappingToDatabase] - public IProject Project - { - get - { - return _repository.DataExportRepository.GetObjectByID(Project_ID); - } } + public IProject Project => _repository.DataExportRepository.GetObjectByID(Project_ID); [NoMappingToDatabase] - public AutomateExtraction[] AutomateExtractions { get - { - return _repository.GetAllObjectsWithParent(this); - } } - + public AutomateExtraction[] AutomateExtractions => _repository.GetAllObjectsWithParent(this); #endregion public AutomateExtractionSchedule(PluginRepository repository, IProject project) { _repository = (AutomateExtractionRepository) repository; - repository.InsertAndHydrate(this, new Dictionary() + repository.InsertAndHydrate(this, new Dictionary { {"Project_ID",project.ID}, - {"Name","New Schedule"+Guid.NewGuid()}, + {"Name", $"New Schedule{Guid.NewGuid()}" }, {"ExecutionTimescale",AutomationTimeScale.Never}, {"ExecutionTimeOfDay","12:00:00"} }); @@ -191,23 +173,20 @@ public void CheckTicketing(ICheckNotifier notifier) return; } - Exception exc; - string reason; - - var evaluation = config.GetDataReleaseabilityOfTicket(Ticket, null, null, out reason, out exc); + var evaluation = config.GetDataReleaseabilityOfTicket(Ticket, null, null, out var reason, out var exc); if(evaluation == TicketingReleaseabilityEvaluation.Releaseable) return; notifier.OnCheckPerformed( new CheckEventArgs( - "Ticket '" + Ticket + "' is " + evaluation + ". Reason given was:" + Environment.NewLine + reason, + $"Ticket '{Ticket}' is {evaluation}. Reason given was:{Environment.NewLine}{reason}", CheckResult.Fail, exc)); } public IExtractionConfiguration[] GetImportableExtractionConfigurations() { - var idsAlreadyPartOfSchedule = AutomateExtractions.Select(e => e.ExtractionConfiguration_ID); + var idsAlreadyPartOfSchedule = AutomateExtractions.Select(e => e.ExtractionConfigurationId); var available = Project.ExtractionConfigurations; diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/QueuedExtraction.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/QueuedExtraction.cs index 62b654d..f8be836 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/QueuedExtraction.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/QueuedExtraction.cs @@ -23,54 +23,43 @@ public class QueuedExtraction : DatabaseEntity public int ExtractionConfiguration_ID { - get { return _extractionConfiguration_ID; } - set { SetField(ref _extractionConfiguration_ID, value); } + get => _extractionConfiguration_ID; + set => SetField(ref _extractionConfiguration_ID, value); } public int Pipeline_ID { - get { return _pipeline_ID; } - set { SetField(ref _pipeline_ID, value); } + get => _pipeline_ID; + set => SetField(ref _pipeline_ID, value); } public DateTime DueDate { - get { return _dueDate; } - set { SetField(ref _dueDate, value); } + get => _dueDate; + set => SetField(ref _dueDate, value); } public string Requester { - get { return _requester; } - set { SetField(ref _requester, value); } + get => _requester; + set => SetField(ref _requester, value); } public DateTime RequestDate { - get { return _requestDate; } - set { SetField(ref _requestDate, value); } + get => _requestDate; + set => SetField(ref _requestDate, value); } #endregion #region Relationships [NoMappingToDatabase] - public IExtractionConfiguration ExtractionConfiguration - { - get - { - return ((AutomateExtractionRepository)Repository).DataExportRepository.GetObjectByID(ExtractionConfiguration_ID); - } - } + public IExtractionConfiguration ExtractionConfiguration => ((AutomateExtractionRepository)Repository).DataExportRepository.GetObjectByID(ExtractionConfiguration_ID); [NoMappingToDatabase] - public Pipeline Pipeline - { - get - { - return ((AutomateExtractionRepository)Repository).CatalogueRepository.GetObjectByID(Pipeline_ID); - } - } + public Pipeline Pipeline => ((AutomateExtractionRepository)Repository).CatalogueRepository.GetObjectByID(Pipeline_ID); + #endregion public QueuedExtraction(PluginRepository repository, ExtractionConfiguration configuration, IPipeline extractionPipeline, DateTime dueDate) { - repository.InsertAndHydrate(this, new Dictionary() + repository.InsertAndHydrate(this, new Dictionary { {"ExtractionConfiguration_ID",configuration.ID}, {"Pipeline_ID",extractionPipeline.ID}, diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/Repository/AutomateExtractionRepositoryFinder.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/Repository/AutomateExtractionRepositoryFinder.cs index cab8bd5..bdd0b24 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/Repository/AutomateExtractionRepositoryFinder.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/Repository/AutomateExtractionRepositoryFinder.cs @@ -25,15 +25,13 @@ public override PluginRepository GetRepositoryIfAny() var compatibleServers = RepositoryLocator.CatalogueRepository.GetAllObjects() .Where(e => e.WasCreatedBy(patcher)).ToArray(); - if (compatibleServers.Length > 1) - throw new Exception("There are 2+ ExternalDatabaseServers of type '" + patcher.Name + - "'. This is not allowed, you must delete one. The servers were called:" + - string.Join(",", compatibleServers.Select(s => s.ToString()))); - - if (compatibleServers.Length == 0) - return null; - - return new AutomateExtractionRepository(RepositoryLocator, compatibleServers[0]); + return compatibleServers.Length switch + { + > 1 => throw new Exception( + $"There are 2+ ExternalDatabaseServers of type '{patcher.Name}'. This is not allowed, you must delete one. The servers were called:{string.Join(",", compatibleServers.Select(s => s.ToString()))}"), + 0 => null, + _ => new AutomateExtractionRepository(RepositoryLocator, compatibleServers[0]) + }; } public override Type GetRepositoryType() diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/SuccessfullyExtractedResults.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/SuccessfullyExtractedResults.cs index 935f9dc..43a1c29 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/SuccessfullyExtractedResults.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Data/SuccessfullyExtractedResults.cs @@ -17,24 +17,24 @@ public class SuccessfullyExtractedResults : DatabaseEntity public string SQL { - get { return _sQL; } - set { SetField(ref _sQL, value); } + get => _sQL; + set => SetField(ref _sQL, value); } public int ExtractableDataSet_ID { - get { return _extractableDataSet_ID; } - set { SetField(ref _extractableDataSet_ID, value); } + get => _extractableDataSet_ID; + set => SetField(ref _extractableDataSet_ID, value); } public int AutomateExtraction_ID { - get { return _automateExtraction_ID; } - set { SetField(ref _automateExtraction_ID, value); } + get => _automateExtraction_ID; + set => SetField(ref _automateExtraction_ID, value); } #endregion public SuccessfullyExtractedResults(PluginRepository repository, string sql, AutomateExtraction parent, IExtractableDataSet dataset) { - repository.InsertAndHydrate(this, new Dictionary() + repository.InsertAndHydrate(this, new Dictionary { {"SQL",sql}, {"ExtractableDataSet_ID",dataset.ID}, diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/ExecuteCommandCreateNewAutomateExtraction.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/ExecuteCommandCreateNewAutomateExtraction.cs index 45e7bfe..c663c8a 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/ExecuteCommandCreateNewAutomateExtraction.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/ExecuteCommandCreateNewAutomateExtraction.cs @@ -36,10 +36,9 @@ public ExecuteCommandCreateNewAutomateExtraction(IBasicActivateItems activator,I ExtractionConfiguration = extractionConfiguration; var existingAutomateExtractions = AutomationRepository.GetAllObjects(); - if (existingAutomateExtractions.Any(s => s.ExtractionConfiguration_ID == extractionConfiguration.ID)) + if (existingAutomateExtractions.Any(s => s.ExtractionConfigurationId == extractionConfiguration.ID)) { SetImpossible($"Configuration already has a {nameof(AutomateExtraction)}"); - return; } } diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/AutomatedExtractionPipelineChecker.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/AutomatedExtractionPipelineChecker.cs index bd2ef1d..02c4b07 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/AutomatedExtractionPipelineChecker.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/AutomatedExtractionPipelineChecker.cs @@ -27,7 +27,8 @@ public void Check(ICheckNotifier notifier) if (_automateExtractionPipeline.PipelineComponents.Any(c => c.Class == typeof (SuccessfullyExtractedResultsDocumenter).FullName)) notifier.OnCheckPerformed(new CheckEventArgs("Found SuccessfullyExtractedResultsDocumenter plugin component",CheckResult.Success)); else - notifier.OnCheckPerformed(new CheckEventArgs("Automated Extraction can only take place through Pipelines that include a "+typeof(SuccessfullyExtractedResultsDocumenter).Name+" plugin component", CheckResult.Fail)); + notifier.OnCheckPerformed(new CheckEventArgs( + $"Automated Extraction can only take place through Pipelines that include a {nameof(SuccessfullyExtractedResultsDocumenter)} plugin component", CheckResult.Fail)); var source = _automateExtractionPipeline.Source; @@ -38,14 +39,12 @@ public void Check(ICheckNotifier notifier) } if (source.Class == typeof (BaselineHackerExecuteDatasetExtractionSource).FullName) - notifier.OnCheckPerformed(new CheckEventArgs("Found Compatible Source " + source.Class, + notifier.OnCheckPerformed(new CheckEventArgs($"Found Compatible Source {source.Class}", CheckResult.Success)); else notifier.OnCheckPerformed( new CheckEventArgs( - "Source Component " + source.Class + " of Pipeline " + _automateExtractionPipeline + - " is not a " + typeof (BaselineHackerExecuteDatasetExtractionSource).FullName + - " (Deltas will never be created)", CheckResult.Warning)); + $"Source Component {source.Class} of Pipeline {_automateExtractionPipeline} is not a {typeof(BaselineHackerExecuteDatasetExtractionSource).FullName} (Deltas will never be created)", CheckResult.Warning)); } catch (Exception e) { diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/BaselineHackerExecuteDatasetExtractionSource.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/BaselineHackerExecuteDatasetExtractionSource.cs index c1681e8..f021c81 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/BaselineHackerExecuteDatasetExtractionSource.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/BaselineHackerExecuteDatasetExtractionSource.cs @@ -11,26 +11,16 @@ public class BaselineHackerExecuteDatasetExtractionSource : ExecuteDatasetExtrac public override string HackExtractionSQL(string sql, IDataLoadEventListener listener) { var finder = new AutomateExtractionRepositoryFinder(new RepositoryProvider(Request.DataExportRepository)); - var repository = (AutomateExtractionRepository) finder.GetRepositoryIfAny(); + var repository = (AutomateExtractionRepository)finder.GetRepositoryIfAny() ?? throw new Exception( + "AutomateExtractionRepositoryFinder returned null, are you missing an AutomationPlugins database"); + var hacker = new DeltaHacker(repository,Request); - if(repository == null) - throw new Exception("AutomateExtractionRepositoryFinder returned null, are you missing an AutomationPlugins database"); + //hacking allowed? + if (hacker.ExecuteHackIfAllowed(listener, out var hackSql) != BaselineHackEvaluation.Allowed) return sql; + var newSql = sql + hackSql; + listener.OnNotify(this,new NotifyEventArgs(ProgressEventType.Information, + $"Full Hacked Query is now:{Environment.NewLine}{newSql}")); - DeltaHacker hacker = new DeltaHacker(repository,Request); - - string hackSql; - - //hacking allowed - if (hacker.ExecuteHackIfAllowed(listener, out hackSql) == BaselineHackEvaluation.Allowed) - { - var newSql = sql + hackSql; - listener.OnNotify(this,new NotifyEventArgs(ProgressEventType.Information, "Full Hacked Query is now:" + Environment.NewLine + newSql)); - - return newSql; - } - - - //no hacking allowed - return sql; + return newSql; } } \ No newline at end of file diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/DeltaHacker.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/DeltaHacker.cs index d32f538..0295031 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/DeltaHacker.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/DeltaHacker.cs @@ -27,7 +27,7 @@ public BaselineHackEvaluation ExecuteHackIfAllowed(IDataLoadEventListener listen //dataset must have a single hic_validFrom field - ColumnInfo validFromField = GetValidFromField(listener); + var validFromField = GetValidFromField(listener); hackSql = null; if(validFromField == null) @@ -39,11 +39,13 @@ public BaselineHackEvaluation ExecuteHackIfAllowed(IDataLoadEventListener listen var configId = _extractDatasetCommand.Configuration.ID; //find the automation record - var automateExtraction = _repository.GetAllObjects("WHERE ExtractionConfiguration_ID = " + configId ); + var automateExtraction = _repository.GetAllObjects( + $"WHERE ExtractionConfiguration_ID = {configId}"); //there should be one! and only 1 if(automateExtraction.Length != 1) - throw new Exception("No AutomateExtraction was found for ExtractionConfiguration '" + _extractDatasetCommand.Configuration +"'"); + throw new Exception( + $"No AutomateExtraction was found for ExtractionConfiguration '{_extractDatasetCommand.Configuration}'"); if(automateExtraction[0].BaselineDate == null) return BaselineHackEvaluation.NoCompatibleBaselineAvailable; @@ -62,7 +64,8 @@ public BaselineHackEvaluation ExecuteHackIfAllowed(IDataLoadEventListener listen //nope, the SQL is different, maybe the user has snuck in an extra column or some other thing if(!string.Equals(currentSQL.Trim(),oldSQL.Trim(),StringComparison.CurrentCultureIgnoreCase)) { - listener.OnNotify(this,new NotifyEventArgs(ProgressEventType.Warning, "SQL is out of date for baseline of " + _extractDatasetCommand + ". The old success will be deleted now and a new baseline will be executed", + listener.OnNotify(this,new NotifyEventArgs(ProgressEventType.Warning, + $"SQL is out of date for baseline of {_extractDatasetCommand}. The old success will be deleted now and a new baseline will be executed", new ExpectedIdenticalStringsException("SQL strings did not match",currentSQL.Trim().ToLower(),oldSQL.Trim().ToLower()))); //either way the SQL is screwey so lets nuke the baseline and make them do a full baseline @@ -81,27 +84,28 @@ public BaselineHackEvaluation ExecuteHackIfAllowed(IDataLoadEventListener listen var tblForJoin = _repository.DiscoveredServer.GetCurrentDatabase().ExpectTable("ReleaseIdentifiersSeen"); if(!tblForJoin.Exists()) - throw new Exception("Table '" + tblForJoin + " did not exist!"); + throw new Exception($"Table '{tblForJoin} did not exist!"); var tableForJoinName = tblForJoin.GetFullyQualifiedName(); - hackSql = @" + hackSql = $@" AND ( ( --new cohorts - " + cohortReleaseIdentifier + @" - not in (Select ReleaseId from " + tableForJoinName + @" where AutomateExtraction_ID = "+ automateExtraction[0].ID+ @") + {cohortReleaseIdentifier} + not in (Select ReleaseId from {tableForJoinName} where AutomateExtraction_ID = {automateExtraction[0].ID}) ) OR ( --new records - " + validFromField.Name + " > '" + automateExtraction[0].BaselineDate.Value + @"' + {validFromField.Name} > '{automateExtraction[0].BaselineDate.Value}' ) ) "; - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Generated the following Delta Hack SQL:" + Environment.NewLine + hackSql)); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Generated the following Delta Hack SQL:{Environment.NewLine}{hackSql}")); return BaselineHackEvaluation.Allowed; } @@ -109,7 +113,7 @@ not in (Select ReleaseId from " + tableForJoinName + @" where AutomateExtraction public ColumnInfo GetValidFromField(IDataLoadEventListener listener) { ColumnInfo validFromField = null; - bool wasPrimaryExtractionTable = false; + var wasPrimaryExtractionTable = false; const string validFromFieldName = SpecialFieldNames.ValidFrom; foreach (TableInfo tableInfo in _extractDatasetCommand.QueryBuilder.TablesUsedInQuery) @@ -119,7 +123,8 @@ public ColumnInfo GetValidFromField(IDataLoadEventListener listener) //table doesn't have a if(col == null) { - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "TableInfo " + tableInfo + " did not have a ColumnInfo called '" + validFromFieldName + "'")); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"TableInfo {tableInfo} did not have a ColumnInfo called '{validFromFieldName}'")); continue; } @@ -136,8 +141,8 @@ public ColumnInfo GetValidFromField(IDataLoadEventListener listener) if (tableInfo.IsPrimaryExtractionTable) { //should never happen to be honest I'm pretty sure QueryBuilder will be super angry if you have 2+ TableInfos both with IsPrimary and ColumnNames should be unique anyway but who knows - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, - "There were multiple ColumnInfos called " + validFromFieldName + " both from IsPrimaryExtractionTable TableInfos (" + validFromField + "," + col + ")" + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, + $"There were multiple ColumnInfos called {validFromFieldName} both from IsPrimaryExtractionTable TableInfos ({validFromField},{col})" )); return null; } @@ -155,9 +160,8 @@ public ColumnInfo GetValidFromField(IDataLoadEventListener listener) else { //neither the previous or ourselves are primary - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, "There were multiple ColumnInfos called " + validFromFieldName + - " (" + validFromField + - "," + col + ") try setting one of your TableInfos to IsPrimaryExtractionTable")); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, + $"There were multiple ColumnInfos called {validFromFieldName} ({validFromField},{col}) try setting one of your TableInfos to IsPrimaryExtractionTable")); return null; } } @@ -167,11 +171,13 @@ public ColumnInfo GetValidFromField(IDataLoadEventListener listener) if (validFromField == null) { - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "No ColumnInfos were found called '" + validFromFieldName + "'")); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, + $"No ColumnInfos were found called '{validFromFieldName}'")); return null; } - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Found valid from field '" + validFromField + "'")); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Found valid from field '{validFromField}'")); return validFromField; } } diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/IdentifierAccumulator.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/IdentifierAccumulator.cs index ebe6b6a..8d2a184 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/IdentifierAccumulator.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/IdentifierAccumulator.cs @@ -28,7 +28,7 @@ public static IdentifierAccumulator GetInstance(DataLoadInfo dataLoadInfo) private IdentifierAccumulator() { - _commitTblName = "Temp"+Guid.NewGuid().ToString().Replace("-", ""); + _commitTblName = $"Temp{Guid.NewGuid().ToString().Replace("-", "")}"; } HashSet identifiers = new HashSet(); @@ -49,9 +49,9 @@ public void CommitCurrentState(AutomateExtractionRepository repository, Automate dt.Columns.Add("AutomateExtraction_ID", typeof(int)); dt.Columns.Add("ReleaseID", typeof(string)); - int id = automateExtraction.ID; + var id = automateExtraction.ID; - foreach (string s in identifiers) + foreach (var s in identifiers) dt.Rows.Add(id, s); //clear old history @@ -60,7 +60,7 @@ public void CommitCurrentState(AutomateExtractionRepository repository, Automate using (var con = repository.DiscoveredServer.GetConnection()) { con.Open(); - var query = "SELECT TOP 0 * INTO " + tempTable.GetFullyQualifiedName()+" FROM ReleaseIdentifiersSeen"; + var query = $"SELECT TOP 0 * INTO {tempTable.GetFullyQualifiedName()} FROM ReleaseIdentifiersSeen"; repository.DiscoveredServer.GetCommand(query, con).ExecuteNonQuery(); } @@ -70,17 +70,17 @@ public void CommitCurrentState(AutomateExtractionRepository repository, Automate } //clear old history - using (SqlConnection con = new SqlConnection(repository.ConnectionString)) + using (var con = new SqlConnection(repository.ConnectionString)) { con.Open(); - string sql = @"INSERT ReleaseIdentifiersSeen (AutomateExtraction_ID, ReleaseID) + var sql = $@"INSERT ReleaseIdentifiersSeen (AutomateExtraction_ID, ReleaseID) SELECT AutomateExtraction_ID, ReleaseID -FROM "+_commitTblName+@" +FROM {_commitTblName} WHERE NOT EXISTS (SELECT 1 FROM ReleaseIdentifiersSeen A2 WHERE -A2.AutomateExtraction_ID = " + _commitTblName + @".AutomateExtraction_ID +A2.AutomateExtraction_ID = {_commitTblName}.AutomateExtraction_ID AND -A2.ReleaseID = " + _commitTblName + @".ReleaseID )"; - SqlCommand cmd = new SqlCommand(sql, con); +A2.ReleaseID = {_commitTblName}.ReleaseID )"; + var cmd = new SqlCommand(sql, con); cmd.ExecuteNonQuery(); } diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/SuccessfullyExtractedResultsDocumenter.cs b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/SuccessfullyExtractedResultsDocumenter.cs index deed874..922b581 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/SuccessfullyExtractedResultsDocumenter.cs +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/Execution/ExtractionPipeline/SuccessfullyExtractedResultsDocumenter.cs @@ -29,17 +29,13 @@ public class SuccessfullyExtractedResultsDocumenter : IPluginDataFlowComponent ProcessPipelineData(ds, toProcess, listener, cancellationToken), + ExtractGlobalsCommand global => ProcessPipelineData(global, toProcess, listener, cancellationToken), + _ => throw new NotSupportedException( + "Expected IExtractCommand to be ExtractDatasetCommand or ExtractGlobalsCommand") + }; } private DataTable ProcessPipelineData(ExtractDatasetCommand ds, DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) @@ -55,34 +51,30 @@ private DataTable ProcessPipelineData(ExtractDatasetCommand ds, DataTable toProc if(_repo == null) throw new Exception("Could not create AutomateExtractionRepository, are you missing an AutomationPluginsDatabase?"); - var matches = _repo.GetAllObjects("WHERE ExtractionConfiguration_ID = " + ds.Configuration.ID); + var matches = _repo.GetAllObjects( + $"WHERE ExtractionConfiguration_ID = {ds.Configuration.ID}"); if(matches.Length == 0) - throw new Exception("ExtractionConfiguration '" + ds.Configuration + "' does not have an entry in the AutomateExtractionRepository"); + throw new Exception( + $"ExtractionConfiguration '{ds.Configuration}' does not have an entry in the AutomateExtractionRepository"); //index ensure you can't have multiple so this shouldn't blow up _automateExtraction = matches.Single(); //delete any old baseline records var success = _automateExtraction.GetSuccessIfAnyFor(ds.DatasetBundle.DataSet); - if (success != null) - success.DeleteInDatabase(); + success?.DeleteInDatabase(); _accumulator = IdentifierAccumulator.GetInstance(_dataLoadInfo); } - foreach (ReleaseIdentifierSubstitution substitution in ds.ReleaseIdentifierSubstitutions) + foreach (var value in ds.ReleaseIdentifierSubstitutions + .SelectMany(substitution => toProcess.Rows.Cast(), + (substitution, dr) => dr[substitution.GetRuntimeName()]) + .Where(value => value != null && value != DBNull.Value)) { - foreach (DataRow dr in toProcess.Rows) - { - var value = dr[substitution.GetRuntimeName()]; - - if(value == null || value == DBNull.Value) - continue; - - _accumulator.AddIdentifierIfNotSee(value.ToString()); - } + _accumulator.AddIdentifierIfNotSee(value.ToString()); } return toProcess; @@ -90,7 +82,8 @@ private DataTable ProcessPipelineData(ExtractDatasetCommand ds, DataTable toProc private DataTable ProcessPipelineData(ExtractGlobalsCommand globalData, DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) { - listener.OnNotify(this,new NotifyEventArgs(ProgressEventType.Warning, "Global Data is not audited and supported by " + GetType().Name)); + listener.OnNotify(this,new NotifyEventArgs(ProgressEventType.Warning, + $"Global Data is not audited and supported by {GetType().Name}")); //we don't do these yet return toProcess; @@ -99,10 +92,10 @@ private DataTable ProcessPipelineData(ExtractGlobalsCommand globalData, DataTabl public void Dispose(IDataLoadEventListener listener, Exception pipelineFailureExceptionIfAny) { - //it completed succesfully right? + //it completed successfully right? if (pipelineFailureExceptionIfAny == null && _dataset != null) { - var successRecord = new SuccessfullyExtractedResults(_repo, _sql, _automateExtraction, _dataset); + _ = new SuccessfullyExtractedResults(_repo, _sql, _automateExtraction, _dataset); _accumulator.CommitCurrentState(_repo, _automateExtraction); } } diff --git a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/LoadModules.Extensions.AutomationPlugins.csproj b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/LoadModules.Extensions.AutomationPlugins.csproj index 74d5fb9..f2b7318 100644 --- a/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/LoadModules.Extensions.AutomationPlugins.csproj +++ b/AutomationPlugins/LoadModules.Extensions.AutomationPlugins/LoadModules.Extensions.AutomationPlugins.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 ..\..\ false LoadModules.Extensions.AutomationPlugins @@ -31,7 +31,7 @@ - + diff --git a/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohort.cs b/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohort.cs index 09c9e0d..db8486f 100644 --- a/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohort.cs +++ b/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohort.cs @@ -39,12 +39,14 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener if (ConfigurationGetter.OverrideReleaseIdentifier != null) { - string replacementName = new MicrosoftQuerySyntaxHelper().GetRuntimeName(ConfigurationGetter.ChosenCohort.GetReleaseIdentifier()); + var replacementName = MicrosoftQuerySyntaxHelper.Instance.GetRuntimeName(ConfigurationGetter.ChosenCohort.GetReleaseIdentifier()); if (!toProcess.Columns.Contains(ConfigurationGetter.OverrideReleaseIdentifier)) - throw new ArgumentException("Cannot DeAnonymise cohort because you specified OverrideReleaseIdentifier of '" + ConfigurationGetter.OverrideReleaseIdentifier + "' but the DataTable toProcess did not contain a column of that name"); + throw new ArgumentException( + $"Cannot DeAnonymise cohort because you specified OverrideReleaseIdentifier of '{ConfigurationGetter.OverrideReleaseIdentifier}' but the DataTable toProcess did not contain a column of that name"); - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Renaming DataTable column " + ConfigurationGetter.OverrideReleaseIdentifier + " to " + replacementName)); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Renaming DataTable column {ConfigurationGetter.OverrideReleaseIdentifier} to {replacementName}")); toProcess.Columns[ConfigurationGetter.OverrideReleaseIdentifier].ColumnName = replacementName; } diff --git a/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohortUI.cs b/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohortUI.cs index 15833d5..e4df8c4 100644 --- a/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohortUI.cs +++ b/Interactive/LoadModules.Extensions.Interactive/DeAnonymise/DeAnonymiseAgainstCohortUI.cs @@ -16,15 +16,15 @@ namespace LoadModules.Extensions.Interactive.DeAnonymise; public partial class DeAnonymiseAgainstCohortUI : Form, IDeAnonymiseAgainstCohortConfigurationFulfiller { private readonly DataTable _toProcess; - private readonly IBasicActivateItems activator; - private IDataExportRepository _dataExportRepository; + private readonly IBasicActivateItems _activator; + private readonly IDataExportRepository _dataExportRepository; public IExtractableCohort ChosenCohort { get; set; } public string OverrideReleaseIdentifier { get; set; } public DeAnonymiseAgainstCohortUI(DataTable toProcess, IBasicActivateItems activator) { _toProcess = toProcess; - this.activator = activator; + _activator = activator; InitializeComponent(); try @@ -42,8 +42,10 @@ public DeAnonymiseAgainstCohortUI(DataTable toProcess, IBasicActivateItems activ foreach (DataColumn column in toProcess.Columns) { - Button b = new Button(); - b.Text = column.ColumnName; + var b = new Button + { + Text = column.ColumnName + }; flowLayoutPanel1.Controls.Add(b); b.Click += b_Click; } @@ -59,28 +61,25 @@ void b_Click(object sender, EventArgs e) private void btnChooseCohort_Click(object sender, EventArgs e) { var dialog = new SelectDialog( - new DialogArgs() {WindowTitle = "Choose Cohort" }, (IActivateItems)activator, _dataExportRepository.GetAllObjects(), false); + new DialogArgs {WindowTitle = "Choose Cohort" }, (IActivateItems)_activator, _dataExportRepository.GetAllObjects(), false); - if(dialog.ShowDialog() == DialogResult.OK) - if (dialog.Selected != null) - { - ChosenCohort = (ExtractableCohort)dialog.Selected; - CheckCohortHasCorrectColumns(); - } + if (dialog.ShowDialog() != DialogResult.OK) return; + if (dialog.Selected == null) return; + ChosenCohort = (ExtractableCohort)dialog.Selected; + CheckCohortHasCorrectColumns(); } private void CheckCohortHasCorrectColumns() { - string release = OverrideReleaseIdentifier ?? new MicrosoftQuerySyntaxHelper().GetRuntimeName(ChosenCohort.GetReleaseIdentifier()); + var release = OverrideReleaseIdentifier ?? MicrosoftQuerySyntaxHelper.Instance.GetRuntimeName(ChosenCohort.GetReleaseIdentifier()); if (!_toProcess.Columns.Contains(release)) checksUI1.OnCheckPerformed( new CheckEventArgs( - "Cannot deanonymise table because it contains no release identifier field (should be called " + - release + ")", CheckResult.Fail)); + $"Cannot deanonymise table because it contains no release identifier field (should be called {release})", CheckResult.Fail)); else - checksUI1.OnCheckPerformed(new CheckEventArgs("Found column " + release + " in your DataTable", + checksUI1.OnCheckPerformed(new CheckEventArgs($"Found column {release} in your DataTable", CheckResult.Success)); lblExpectedReleaseIdentifierColumn.Text = release; @@ -99,7 +98,7 @@ private void cbOverrideReleaseIdentifierColumn_CheckedChanged(object sender, Eve private void btnOk_Click(object sender, EventArgs e) { DialogResult = DialogResult.OK; - this.Close(); + Close(); } private void button1_Click(object sender, EventArgs e) @@ -107,7 +106,7 @@ private void button1_Click(object sender, EventArgs e) ChosenCohort = null; OverrideReleaseIdentifier = null; DialogResult = DialogResult.Cancel; - this.Close(); + Close(); } } diff --git a/Interactive/LoadModules.Extensions.Interactive/LoadModules.Extensions.Interactive.csproj b/Interactive/LoadModules.Extensions.Interactive/LoadModules.Extensions.Interactive.csproj index 6876f64..b55c6e4 100644 --- a/Interactive/LoadModules.Extensions.Interactive/LoadModules.Extensions.Interactive.csproj +++ b/Interactive/LoadModules.Extensions.Interactive/LoadModules.Extensions.Interactive.csproj @@ -1,6 +1,6 @@  - net6.0-windows + net7.0-windows ..\..\ false true @@ -14,10 +14,11 @@ - - + + + Form diff --git a/LoadModules.Extensions.Tests/AutomationTests/AllowAnythingTicketing.cs b/LoadModules.Extensions.Tests/AutomationTests/AllowAnythingTicketing.cs index 1108491..f052666 100644 --- a/LoadModules.Extensions.Tests/AutomationTests/AllowAnythingTicketing.cs +++ b/LoadModules.Extensions.Tests/AutomationTests/AllowAnythingTicketing.cs @@ -1,11 +1,9 @@ using System; -using System.ComponentModel.Composition; using Rdmp.Core.Ticketing; using Rdmp.Core.ReusableLibraryCode.Checks; namespace LoadModules.Extensions.AutomationPlugins.Tests; -[Export(typeof(ITicketingSystem))] public class AllowAnythingTicketing:ITicketingSystem { public void Check(ICheckNotifier notifier) @@ -31,8 +29,5 @@ public TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(string ma return TicketingReleaseabilityEvaluation.Releaseable; } - public string GetProjectFolderName(string masterTicket) - { - return "Project " + masterTicket; - } + public string GetProjectFolderName(string masterTicket) => $"Project {masterTicket}"; } \ No newline at end of file diff --git a/LoadModules.Extensions.Tests/AutomationTests/NeverAllowAnythingTicketing.cs b/LoadModules.Extensions.Tests/AutomationTests/NeverAllowAnythingTicketing.cs index f2172fb..e8b3eec 100644 --- a/LoadModules.Extensions.Tests/AutomationTests/NeverAllowAnythingTicketing.cs +++ b/LoadModules.Extensions.Tests/AutomationTests/NeverAllowAnythingTicketing.cs @@ -1,11 +1,9 @@ using System; -using System.ComponentModel.Composition; using Rdmp.Core.Ticketing; using Rdmp.Core.ReusableLibraryCode.Checks; namespace LoadModules.Extensions.AutomationPlugins.Tests; -[Export(typeof(ITicketingSystem))] public class NeverAllowAnythingTicketing:ITicketingSystem { public void Check(ICheckNotifier notifier) @@ -31,8 +29,5 @@ public TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(string ma return TicketingReleaseabilityEvaluation.NotReleaseable; } - public string GetProjectFolderName(string masterTicket) - { - return "Project " + masterTicket; - } + public string GetProjectFolderName(string masterTicket) => $"Project {masterTicket}"; } \ No newline at end of file diff --git a/LoadModules.Extensions.Tests/AutomationTests/ObjectCreationTests.cs b/LoadModules.Extensions.Tests/AutomationTests/ObjectCreationTests.cs index 84bd31c..1b6ab27 100644 --- a/LoadModules.Extensions.Tests/AutomationTests/ObjectCreationTests.cs +++ b/LoadModules.Extensions.Tests/AutomationTests/ObjectCreationTests.cs @@ -23,13 +23,15 @@ public void CreateAllObjects() Assert.AreEqual(schedule.Project_ID , proj.ID); //Configurations - var config = new ExtractionConfiguration(Repo.DataExportRepository, proj); - config.Name = "Configuration1"; + var config = new ExtractionConfiguration(Repo.DataExportRepository, proj) + { + Name = "Configuration1" + }; config.SaveToDatabase(); //Permission to use a given configuration - AutomateExtraction automate = new AutomateExtraction(Repo,schedule,config); - Assert.AreEqual(automate.ExtractionConfiguration_ID,config.ID); + var automate = new AutomateExtraction(Repo,schedule,config); + Assert.AreEqual(automate.ExtractionConfigurationId,config.ID); Assert.AreEqual(automate.Disabled ,false); Assert.IsNull(automate.BaselineDate); @@ -49,10 +51,10 @@ public void CreateAllObjects() [Test] public void CreateQueuedExecution() { - Project project = new Project(Repo.DataExportRepository,"proj"); - ExtractionConfiguration configuration= new ExtractionConfiguration(Repo.DataExportRepository,project); + var project = new Project(Repo.DataExportRepository,"proj"); + var configuration= new ExtractionConfiguration(Repo.DataExportRepository,project); - Pipeline p = new Pipeline(Repo.CatalogueRepository); + var p = new Pipeline(Repo.CatalogueRepository); var que = new QueuedExtraction(Repo, configuration, p, DateTime.Now.AddHours(1)); Assert.IsTrue(que.Exists()); diff --git a/LoadModules.Extensions.Tests/AutomationTests/TestsRequiringAnAutomationPluginRepository.cs b/LoadModules.Extensions.Tests/AutomationTests/TestsRequiringAnAutomationPluginRepository.cs index d1c9b1b..8ca4910 100644 --- a/LoadModules.Extensions.Tests/AutomationTests/TestsRequiringAnAutomationPluginRepository.cs +++ b/LoadModules.Extensions.Tests/AutomationTests/TestsRequiringAnAutomationPluginRepository.cs @@ -31,12 +31,14 @@ public static AutomateExtractionRepository CreateAutomationDatabaseStatic(Discov var patcher = new AutomateExtractionPluginPatcher(); - MasterDatabaseScriptExecutor executor = new MasterDatabaseScriptExecutor(db); + var executor = new MasterDatabaseScriptExecutor(db); executor.CreateAndPatchDatabase(patcher, new AcceptAllCheckNotifier()); - var server = new ExternalDatabaseServer(repositoryLocator.CatalogueRepository, "Automation Server", patcher); - server.Server = db.Server.Name; - server.Database = db.GetRuntimeName(); + var server = new ExternalDatabaseServer(repositoryLocator.CatalogueRepository, "Automation Server", patcher) + { + Server = db.Server.Name, + Database = db.GetRuntimeName() + }; server.SaveToDatabase(); return new AutomateExtractionRepository(repositoryLocator, server); @@ -54,7 +56,7 @@ public static Pipeline GetValidExtractionPipelineStatic(ICatalogueRepository cat var source = new PipelineComponent(catalogueRepository, validPipeline, typeof(BaselineHackerExecuteDatasetExtractionSource), 0); source.CreateArgumentsForClassIfNotExists(); - var broadcaster = new PipelineComponent(catalogueRepository, validPipeline, typeof(SuccessfullyExtractedResultsDocumenter), 1); + _=new PipelineComponent(catalogueRepository, validPipeline, typeof(SuccessfullyExtractedResultsDocumenter), 1); var destination = new PipelineComponent(catalogueRepository, validPipeline, typeof(ExecuteDatasetExtractionFlatFileDestination), 2); destination.CreateArgumentsForClassIfNotExists(); diff --git a/LoadModules.Extensions.Tests/Interactive/DeAnonymiseAgainstCohortTests.cs b/LoadModules.Extensions.Tests/Interactive/DeAnonymiseAgainstCohortTests.cs index 1b67ea0..7ce56c5 100644 --- a/LoadModules.Extensions.Tests/Interactive/DeAnonymiseAgainstCohortTests.cs +++ b/LoadModules.Extensions.Tests/Interactive/DeAnonymiseAgainstCohortTests.cs @@ -31,6 +31,7 @@ public void setup() public void Normal_ReleaseDeAnonToPrivateKeys(bool doRedundantOverride) { using var dt = new DataTable(); + dt.BeginLoadData(); dt.Columns.Add("ReleaseID"); dt.Columns.Add("Animal"); @@ -40,8 +41,9 @@ public void Normal_ReleaseDeAnonToPrivateKeys(bool doRedundantOverride) if (doRedundantOverride) OverrideReleaseIdentifier = "ReleaseID"; + dt.EndLoadData(); using var clone = dt.Copy(); // Grab a copy of the pre-pipeline data to compare - var result = _deAnonymiseAgainstCohort.ProcessPipelineData(dt, new ThrowImmediatelyDataLoadEventListener(), new GracefulCancellationToken()); + var result = _deAnonymiseAgainstCohort.ProcessPipelineData(dt, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); Assert.IsTrue(result.Columns.Contains("PrivateID")); @@ -70,7 +72,7 @@ public void Freaky_ColumnNameOverriding() try { using var clone = dt.Copy(); // Grab a copy of the pre-pipeline data to compare - using var result = _deAnonymiseAgainstCohort.ProcessPipelineData(dt, new ThrowImmediatelyDataLoadEventListener(), new GracefulCancellationToken()); + using var result = _deAnonymiseAgainstCohort.ProcessPipelineData(dt, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); Assert.IsTrue(result.Columns.Contains("PrivateID")); @@ -96,7 +98,7 @@ public void Throws_ColumnMissing() foreach (var (key, value) in _cohortKeysGenerated) dt.Rows.Add("fish"); - var ex = Assert.Throws(() => _deAnonymiseAgainstCohort.ProcessPipelineData(dt, new ThrowImmediatelyDataLoadEventListener(), new GracefulCancellationToken())); + var ex = Assert.Throws(() => _deAnonymiseAgainstCohort.ProcessPipelineData(dt, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken())); Assert.IsTrue(ex?.Message.StartsWith("Column 'ReleaseID' does not belong to table"),$"Exception text was '{ex?.Message}'"); } @@ -112,7 +114,7 @@ public void Throws_ColumnMissingWithOverride() OverrideReleaseIdentifier = "HappyFace"; - var ex = Assert.Throws(() => _deAnonymiseAgainstCohort.ProcessPipelineData(dt, new ThrowImmediatelyDataLoadEventListener(), new GracefulCancellationToken())); + var ex = Assert.Throws(() => _deAnonymiseAgainstCohort.ProcessPipelineData(dt, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken())); Assert.AreEqual(ex.Message,"Cannot DeAnonymise cohort because you specified OverrideReleaseIdentifier of 'HappyFace' but the DataTable toProcess did not contain a column of that name"); } diff --git a/LoadModules.Extensions.Tests/LoadModules - Backup.Extensions.Tests.csproj b/LoadModules.Extensions.Tests/LoadModules - Backup.Extensions.Tests.csproj new file mode 100644 index 0000000..1bdadce --- /dev/null +++ b/LoadModules.Extensions.Tests/LoadModules - Backup.Extensions.Tests.csproj @@ -0,0 +1,37 @@ + + + {A6987FAF-E280-48C8-B50D-17FF34924E1D} + net7.0-windows + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + LoadModules.Extensions.Tests + LoadModules.Extensions.Tests + Copyright © 2019 + false + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/LoadModules.Extensions.Tests/LoadModules.Extensions.Tests.csproj b/LoadModules.Extensions.Tests/LoadModules.Extensions.Tests.csproj index eed978e..ffb437e 100644 --- a/LoadModules.Extensions.Tests/LoadModules.Extensions.Tests.csproj +++ b/LoadModules.Extensions.Tests/LoadModules.Extensions.Tests.csproj @@ -1,7 +1,7 @@ {A6987FAF-E280-48C8-B50D-17FF34924E1D} - net6.0-windows + net7.0-windows {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages @@ -17,16 +17,15 @@ - - - - + + + diff --git a/LoadModules.Extensions.Tests/NuspecIsCorrectTests.cs b/LoadModules.Extensions.Tests/NuspecIsCorrectTests.cs index 120951b..e0057a8 100644 --- a/LoadModules.Extensions.Tests/NuspecIsCorrectTests.cs +++ b/LoadModules.Extensions.Tests/NuspecIsCorrectTests.cs @@ -44,18 +44,18 @@ public void TestDependencyCorrect(string csproj, string nuspec, string packagesM Assert.Fail("Could not find file {0}", packagesMarkdown); // - Regex rPackageRef = new Regex(@" - Regex rDependencyRef = new Regex(@"m.Message.Equals("1"))); + Assert.AreEqual(5, toMemory.EventsReceivedBySender[py].Count(m=>m.Message.Equals("1"))); } @@ -66,8 +67,7 @@ public void SlowRollerTest() [Test] public void SlowRollerAsync() { - var script = - @"import time + const string script = @"import time import sys print (""GetReady"") @@ -83,9 +83,11 @@ import sys File.WriteAllText(file.FullName, script); try { - var py = new PythonDataProvider(); - py.FullPathToPythonScriptToRun = file.FullName; - py.Version = PythonVersion.Version3; + var py = new PythonDataProvider + { + FullPathToPythonScriptToRun = file.FullName, + Version = PythonVersion.Version3 + }; var tomemory = new ToMemoryDataLoadJob(); @@ -121,8 +123,7 @@ import sys [Test] public void WriteToErrorAndStandardOut() { - var script = - @"from __future__ import print_function + const string script = @"from __future__ import print_function import sys def eprint(*args, **kwargs): @@ -138,9 +139,11 @@ def eprint(*args, **kwargs): File.WriteAllText(file.FullName, script); try { - var py = new PythonDataProvider(); - py.FullPathToPythonScriptToRun = file.FullName; - py.Version = PythonVersion.Version3; + var py = new PythonDataProvider + { + FullPathToPythonScriptToRun = file.FullName, + Version = PythonVersion.Version3 + }; var tomem = new ToMemoryDataLoadJob(true); py.Fetch(tomem, new GracefulCancellationToken()); diff --git a/LoadModules.Extensions.Tests/Python/Unit/Python2And3InstalledTests.cs b/LoadModules.Extensions.Tests/Python/Unit/Python2And3InstalledTests.cs index 14dc96a..5921d00 100644 --- a/LoadModules.Extensions.Tests/Python/Unit/Python2And3InstalledTests.cs +++ b/LoadModules.Extensions.Tests/Python/Unit/Python2And3InstalledTests.cs @@ -31,15 +31,14 @@ public void PythonScript_OverrideExecutablePath_VersionMismatch() provider.Check(new AcceptAllCheckNotifier());// version 3 should now be installed //version 3 executable path is explicit override for executing commands - provider.OverridePythonExecutablePath = new FileInfo(Path.Combine(provider.GetFullPythonInstallDirectory(), "python.exe")); + provider.OverridePythonExecutablePath = new FileInfo(provider.GetFullPythonPath()); provider.Version = PythonVersion.Version2; //so we now know that version 3 is installed, and we have overriden the python path to the .exe explicitly and we are trying to launch with Version2 enum now var ex = Assert.Throws(()=> { - provider.Check(new ThrowImmediatelyCheckNotifier()); - //provider.Fetch(MockRepository.GenerateStub(), new GracefulCancellationToken()); + provider.Check(ThrowImmediatelyCheckNotifier.Quiet); }); - StringAssert.Contains(@"which is incompatible with the desired version 2.7.1",ex?.Message); + StringAssert.Contains(@"which is incompatible with the desired version 2.7",ex?.Message); } } \ No newline at end of file diff --git a/LoadModules.Extensions.Tests/Python/Unit/Python2InstalledTests.cs b/LoadModules.Extensions.Tests/Python/Unit/Python2InstalledTests.cs index f8959f5..c05e3f3 100644 --- a/LoadModules.Extensions.Tests/Python/Unit/Python2InstalledTests.cs +++ b/LoadModules.Extensions.Tests/Python/Unit/Python2InstalledTests.cs @@ -54,7 +54,7 @@ public void PythonScript_Version2_GoodSyntax(bool wrapFilename) //call with accept all provider.Check(new AcceptAllCheckNotifier()); - provider.Check(new ThrowImmediatelyCheckNotifier() { ThrowOnWarning = true }); + provider.Check(ThrowImmediatelyCheckNotifier.QuietPicky); provider.Fetch(new ThrowImmediatelyDataLoadJob(), new GracefulCancellationToken()); } diff --git a/LoadModules.Extensions.Tests/Python/Unit/Python3InstalledTests.cs b/LoadModules.Extensions.Tests/Python/Unit/Python3InstalledTests.cs index 57fc18f..ccd2117 100644 --- a/LoadModules.Extensions.Tests/Python/Unit/Python3InstalledTests.cs +++ b/LoadModules.Extensions.Tests/Python/Unit/Python3InstalledTests.cs @@ -15,13 +15,15 @@ public class Python3InstalledTests [SetUp] public void IsPython3Installed() { - PythonDataProvider p = new PythonDataProvider(); - p.Version = PythonVersion.Version3; + var p = new PythonDataProvider + { + Version = PythonVersion.Version3 + }; try { - string version = p.GetPythonVersion(); + var version = p.GetPythonVersion(); - Console.WriteLine("Found python version:" + version); + Console.WriteLine($"Found python version:{version}"); } catch (Exception e) { @@ -35,15 +37,17 @@ public void IsPython3Installed() [Test] public void PythonScript_Version3_DodgySyntax() { - string MyPythonScript = @"print 'Hello World'"; + var MyPythonScript = @"print 'Hello World'"; File.Delete("Myscript.py"); File.WriteAllText("Myscript.py", MyPythonScript); - PythonDataProvider provider = new PythonDataProvider(); - provider.Version = PythonVersion.Version3; - provider.FullPathToPythonScriptToRun = "Myscript.py"; - provider.MaximumNumberOfSecondsToLetScriptRunFor = 0; + var provider = new PythonDataProvider + { + Version = PythonVersion.Version3, + FullPathToPythonScriptToRun = "Myscript.py", + MaximumNumberOfSecondsToLetScriptRunFor = 0 + }; //call with accept all provider.Check(new AcceptAllCheckNotifier()); @@ -57,15 +61,17 @@ public void PythonScript_Version3_DodgySyntax() [Test] public void PythonScript_ValidScript() { - string MyPythonScript = @"print (""Hello World"")"; + var MyPythonScript = @"print (""Hello World"")"; File.Delete("Myscript.py"); File.WriteAllText("Myscript.py", MyPythonScript); - PythonDataProvider provider = new PythonDataProvider(); - provider.Version = PythonVersion.Version3; - provider.FullPathToPythonScriptToRun = "Myscript.py"; - provider.MaximumNumberOfSecondsToLetScriptRunFor = 0; + var provider = new PythonDataProvider + { + Version = PythonVersion.Version3, + FullPathToPythonScriptToRun = "Myscript.py", + MaximumNumberOfSecondsToLetScriptRunFor = 0 + }; //call with accept all provider.Check(new AcceptAllCheckNotifier()); diff --git a/LoadModules.Extensions.Tests/Python/Unit/PythonNotInstalledTests.cs b/LoadModules.Extensions.Tests/Python/Unit/PythonNotInstalledTests.cs index 5c41cac..a482ba6 100644 --- a/LoadModules.Extensions.Tests/Python/Unit/PythonNotInstalledTests.cs +++ b/LoadModules.Extensions.Tests/Python/Unit/PythonNotInstalledTests.cs @@ -21,12 +21,12 @@ public void PythonIsNotInstalled(PythonVersion version) Version = version }; - var ex = Assert.Throws(()=>provider.Check(new ThrowImmediatelyCheckNotifier())); + var ex = Assert.Throws(()=>provider.Check(ThrowImmediatelyCheckNotifier.Quiet)); Assert.IsTrue(ex?.Message.Contains("Failed to launch")); } - private void InconclusiveIfPythonIsInstalled(PythonVersion version) + private static void InconclusiveIfPythonIsInstalled(PythonVersion version) { var provider = new PythonDataProvider { diff --git a/LoadModules.Extensions.Tests/Python/Unit/TestsThatWorkRegardless.cs b/LoadModules.Extensions.Tests/Python/Unit/TestsThatWorkRegardless.cs index 8d09665..08bf69b 100644 --- a/LoadModules.Extensions.Tests/Python/Unit/TestsThatWorkRegardless.cs +++ b/LoadModules.Extensions.Tests/Python/Unit/TestsThatWorkRegardless.cs @@ -12,9 +12,9 @@ public class TestsThatWorkRegardless [Test] public void PythonVersionNotSetYet() { - PythonDataProvider provider = new PythonDataProvider(); - var ex = Assert.Throws(()=>provider.Check(new ThrowImmediatelyCheckNotifier())); - Assert.AreEqual("Version of Python required for script has not been selected",ex.Message); + var provider = new PythonDataProvider(); + var ex = Assert.Throws(()=>provider.Check(ThrowImmediatelyCheckNotifier.Quiet)); + Assert.AreEqual("Version of Python required for script has not been selected",ex?.Message); } @@ -22,22 +22,24 @@ public void PythonVersionNotSetYet() [Test] public void PythonScript_OverrideExecutablePath_FileDoesntExist() { - string MyPythonScript = @"s = raw_input ('==>')"; + var MyPythonScript = @"s = raw_input ('==>')"; var py = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Myscript.py"); File.Delete(py); File.WriteAllText(py, MyPythonScript); - PythonDataProvider provider = new PythonDataProvider(); - provider.Version = PythonVersion.Version2; - provider.FullPathToPythonScriptToRun = py; - provider.MaximumNumberOfSecondsToLetScriptRunFor = 5; - provider.OverridePythonExecutablePath = new FileInfo(@"C:\fishmongers\python"); + var provider = new PythonDataProvider + { + Version = PythonVersion.Version2, + FullPathToPythonScriptToRun = py, + MaximumNumberOfSecondsToLetScriptRunFor = 5, + OverridePythonExecutablePath = new FileInfo(@"C:\fishmongers\python") + }; //call with accept all var ex = Assert.Throws(()=>provider.Check(new AcceptAllCheckNotifier())); - StringAssert.Contains(@"The specified OverridePythonExecutablePath:C:\fishmongers\python does not exist",ex.Message); + StringAssert.Contains(@"The specified OverridePythonExecutablePath:C:\fishmongers\python does not exist",ex?.Message); } diff --git a/LoadModules.Extensions.sln b/LoadModules.Extensions.sln index e3eff55..33dd0d0 100644 --- a/LoadModules.Extensions.sln +++ b/LoadModules.Extensions.sln @@ -43,6 +43,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "main", "Plugin\main\main.cs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "windows", "Plugin\windows\windows.csproj", "{07F9F874-1306-4FE6-A03F-A483592BBC7F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rdmp.Core", "RDMP\Rdmp.Core\Rdmp.Core.csproj", "{8A840E89-4825-4F3C-B822-6EF946DC9B99}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rdmp.UI", "RDMP\Rdmp.UI\Rdmp.UI.csproj", "{60721BCE-E328-45CF-B6D2-B627364FBBFA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Common", "RDMP\Tests.Common\Tests.Common.csproj", "{9C2B07F4-2996-44BE-8067-7AA94C448A23}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -115,6 +121,30 @@ Global {07F9F874-1306-4FE6-A03F-A483592BBC7F}.Release|Any CPU.Build.0 = Release|Any CPU {07F9F874-1306-4FE6-A03F-A483592BBC7F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {07F9F874-1306-4FE6-A03F-A483592BBC7F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Release|Any CPU.Build.0 = Release|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8A840E89-4825-4F3C-B822-6EF946DC9B99}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Release|Any CPU.Build.0 = Release|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {60721BCE-E328-45CF-B6D2-B627364FBBFA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Release|Any CPU.Build.0 = Release|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9C2B07F4-2996-44BE-8067-7AA94C448A23}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Plugin/main/main.csproj b/Plugin/main/main.csproj index 72b9cde..9c3af33 100644 --- a/Plugin/main/main.csproj +++ b/Plugin/main/main.csproj @@ -1,20 +1,10 @@ - net6.0 + net7.0 Rdmp.Extensions.Plugin Rdmp.Extensions.Plugin Copyright © 2019 - - - - - - - - - - diff --git a/Plugin/windows/windows.csproj b/Plugin/windows/windows.csproj index 1c1be4f..0e28647 100644 --- a/Plugin/windows/windows.csproj +++ b/Plugin/windows/windows.csproj @@ -1,14 +1,11 @@  {07F9F874-1306-4FE6-A03F-A483592BBC7F} - net6.0-windows + net7.0-windows Rdmp.Extensions.Plugin Rdmp.Extensions.Plugin Copyright © 2019 - - - diff --git a/Python/LoadModules.Extensions.Python/DataProvider/PythonDataProvider.cs b/Python/LoadModules.Extensions.Python/DataProvider/PythonDataProvider.cs index 6e5edcb..87dfd7f 100644 --- a/Python/LoadModules.Extensions.Python/DataProvider/PythonDataProvider.cs +++ b/Python/LoadModules.Extensions.Python/DataProvider/PythonDataProvider.cs @@ -3,19 +3,24 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.Versioning; using System.Threading.Tasks; using FAnsi.Discovery; +using Microsoft.Win32; using Rdmp.Core.Curation; using Rdmp.Core.Curation.Data; using Rdmp.Core.DataFlowPipeline; using Rdmp.Core.DataLoad; using Rdmp.Core.DataLoad.Engine.DataProvider; using Rdmp.Core.DataLoad.Engine.Job; +using Rdmp.Core.ReusableLibraryCode.Annotations; using Rdmp.Core.ReusableLibraryCode.Checks; using Rdmp.Core.ReusableLibraryCode.Progress; namespace LoadModules.Extensions.Python.DataProvider; +#nullable enable + public enum PythonVersion { NotSet, @@ -23,26 +28,24 @@ public enum PythonVersion Version3 } -public class PythonDataProvider:IPluginDataProvider +public sealed class PythonDataProvider:IPluginDataProvider { - - [DemandsInitialization("The Python script to run")] - public string FullPathToPythonScriptToRun { get; set; } + public string? FullPathToPythonScriptToRun { get; set; } - [DemandsInitialization("The maximum number of seconds to allow the python script to run for before declaring it a failure, 0 for indefinetly")] + [DemandsInitialization("The maximum number of seconds to allow the python script to run for before declaring it a failure, 0 for indefinitely")] public int MaximumNumberOfSecondsToLetScriptRunFor { get; set; } - [DemandsInitialization("Python version required to run your script")] + [DemandsInitialization("Python version required to run your script")] public PythonVersion Version { get; set; } [DemandsInitialization("Override Python Executable Path")] - public FileInfo OverridePythonExecutablePath { get; set; } + public FileInfo? OverridePythonExecutablePath { get; set; } + - public void LoadCompletedSoDispose(ExitCodeType exitCode, IDataLoadEventListener postLoadEventsListener) { - + } public void Check(ICheckNotifier notifier) @@ -60,14 +63,15 @@ public void Check(ICheckNotifier notifier) { var version = GetPythonVersion(); - if (version.StartsWith(GetExpectedPythonVersion())) + if (version?.StartsWith(GetExpectedPythonVersion(), StringComparison.Ordinal)==true) notifier.OnCheckPerformed( new CheckEventArgs( - $"Found Expected Python version {version} on the host machine at directory {GetFullPythonInstallDirectory()}", CheckResult.Success)); - else if (version.StartsWith(GetCompatiblePythonVersion())) + $"Found Expected Python version {version} on the host machine at {GetPython(Version == PythonVersion.Version2 ? '2' : '3').path}", CheckResult.Success)); + else if (version is not null && ((version[0] == '3' && Version == PythonVersion.Version3) || + (version[0] == '2' && Version == PythonVersion.Version2))) notifier.OnCheckPerformed( new CheckEventArgs( - $"Found Compatible Python version {version} on the host machine at directory {GetFullPythonInstallDirectory()}", CheckResult.Success)); + $"Found Compatible Python version {version} on the host machine at {GetPython(Version == PythonVersion.Version2 ? '2' : '3').path}", CheckResult.Success)); else { notifier.OnCheckPerformed( @@ -88,7 +92,7 @@ public void Check(ICheckNotifier notifier) : new CheckEventArgs(e.Message, CheckResult.Fail, e)); } - if (FullPathToPythonScriptToRun?.Contains(" ")==true && FullPathToPythonScriptToRun?.Contains("\"")==false) + if (FullPathToPythonScriptToRun?.Contains(' ') ==true && FullPathToPythonScriptToRun?.Contains('"') ==false) notifier.OnCheckPerformed( new CheckEventArgs( "FullPathToPythonScriptToRun contains spaces but is not wrapped by quotes which will likely fail when we assemble the python execute command", @@ -101,69 +105,51 @@ public void Check(ICheckNotifier notifier) CheckResult.Warning)); } - public string GetPythonVersion() + public string? GetPythonVersion() { - var info = GetPythonCommand(@"-c ""import sys; print(sys.version)"""); - + const string getVersion = """ + -c "import sys; print(sys.version)" + """; var toMemory = new ToMemoryDataLoadEventListener(true); + var result = ExecuteProcess(toMemory, getVersion, 600); - var result = ExecuteProcess(toMemory, info, 600); - if (result != 0) return null; - + var msg = toMemory.EventsReceivedBySender[this].SingleOrDefault(); if (msg != null) return msg.Message; - throw new Exception($"Call to {info.Arguments} did not return any value but exited with code {result}"); + throw new Exception($"Call to {getVersion} did not return any value but exited with code {result}"); } - private ProcessStartInfo GetPythonCommand(string command) + private string GetPythonCommand() { - string exeFullPath; - if (OverridePythonExecutablePath == null) - { - //e.g. c:\python34 - var installDir = GetFullPythonInstallDirectory(); - exeFullPath = Path.Combine(installDir, "python"); - } - else - { - if (!OverridePythonExecutablePath.Exists) - throw new FileNotFoundException( - $"The specified OverridePythonExecutablePath:{OverridePythonExecutablePath} does not exist"); - else - if(OverridePythonExecutablePath.Name != "python.exe") - throw new FileNotFoundException( - $"The specified OverridePythonExecutablePath:{OverridePythonExecutablePath} file is not called python.exe... what is going on here?"); + return GetPython(Version==PythonVersion.Version2?'2':'3').path; - exeFullPath = OverridePythonExecutablePath.FullName; - } + if (!OverridePythonExecutablePath.Exists) + throw new FileNotFoundException( + $"The specified OverridePythonExecutablePath:{OverridePythonExecutablePath} does not exist"); + if(OverridePythonExecutablePath.Name != "python.exe") + throw new FileNotFoundException( + $"The specified OverridePythonExecutablePath:{OverridePythonExecutablePath} file is not called python.exe... what is going on here?"); - var info = new ProcessStartInfo(exeFullPath) - { - Arguments = command - }; - - return info; + return OverridePythonExecutablePath.FullName; } public void Initialize(ILoadDirectory hicProjectDirectory, DiscoveredDatabase dbInfo) { - + } public ExitCodeType Fetch(IDataLoadJob job, GracefulCancellationToken cancellationToken) { - var processStartInfo = GetPythonCommand(FullPathToPythonScriptToRun); - int exitCode; try { - exitCode = ExecuteProcess(job, processStartInfo,MaximumNumberOfSecondsToLetScriptRunFor); + exitCode = ExecuteProcess(job, FullPathToPythonScriptToRun, MaximumNumberOfSecondsToLetScriptRunFor); } catch (TimeoutException e) { @@ -177,26 +163,32 @@ public ExitCodeType Fetch(IDataLoadJob job, GracefulCancellationToken cancellati return exitCode == 0 ? ExitCodeType.Success : ExitCodeType.Error; } - private int ExecuteProcess(IDataLoadEventListener listener, ProcessStartInfo processStartInfo, int maximumNumberOfSecondsToLetScriptRunFor) + private int ExecuteProcess(IDataLoadEventListener listener, string script, int maximumNumberOfSecondsToLetScriptRunFor) { - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardError = true; - - processStartInfo.UseShellExecute = false; - processStartInfo.CreateNoWindow = true; + var processStartInfo = new ProcessStartInfo + { + FileName = GetPythonCommand(), + Arguments = script, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; - Process p = null; + Process p; var allErrorDataConsumed = false; var allOutputDataConsumed = false; try { - p = new Process(); - p.StartInfo = processStartInfo; - p.OutputDataReceived += (s, e) => allOutputDataConsumed = OutputDataReceived(s, e, listener,false); - p.ErrorDataReceived += (s, e) => allErrorDataConsumed = OutputDataReceived(s, e, listener,true); - + p = new Process + { + StartInfo = processStartInfo + }; + p.OutputDataReceived += (s, e) => allOutputDataConsumed = OutputDataReceived(e, listener,false); + p.ErrorDataReceived += (s, e) => allErrorDataConsumed = OutputDataReceived(e, listener,true); + p.Start(); p.BeginErrorReadLine(); p.BeginOutputReadLine(); @@ -207,11 +199,11 @@ private int ExecuteProcess(IDataLoadEventListener listener, ProcessStartInfo pro throw new Exception( $"Failed to launch:{Environment.NewLine}{processStartInfo.FileName}{Environment.NewLine} with Arguments:{processStartInfo.Arguments}",e); } - + // To avoid deadlocks, always read the output stream first and then wait. var startTime = DateTime.Now; - + while (!p.WaitForExit(100))//while process has not exited { if (!TimeoutExpired(startTime)) continue; //if timeout expired @@ -239,22 +231,22 @@ private int ExecuteProcess(IDataLoadEventListener listener, ProcessStartInfo pro throw new TimeoutException("Timeout expired while waiting for all output streams from the Python process to finish being read"); } - if (outputDataReceivedExceptions.Any()) - if (outputDataReceivedExceptions.Count == 1) - throw outputDataReceivedExceptions[0]; - else - throw new AggregateException(outputDataReceivedExceptions); + lock(this) + if (_outputDataReceivedExceptions.Any()) + throw _outputDataReceivedExceptions.Count == 1 + ? _outputDataReceivedExceptions[0] + : new AggregateException(_outputDataReceivedExceptions); return p.ExitCode; } - List outputDataReceivedExceptions = new List(); + private readonly List _outputDataReceivedExceptions = new(); - private bool OutputDataReceived(object sender, DataReceivedEventArgs e, IDataLoadEventListener listener,bool isErrorStream) + private bool OutputDataReceived(DataReceivedEventArgs e, IDataLoadEventListener listener,bool isErrorStream) { if(e.Data == null) return true; - + lock (this) { try @@ -264,12 +256,12 @@ private bool OutputDataReceived(object sender, DataReceivedEventArgs e, IDataLoa } catch (Exception ex) { - //the notify handler is crashing... lets stop tyring to read data from this async handler. Also add the exception to the list because we don't want it throwing out of this lamda - outputDataReceivedExceptions.Add(ex); + //the notify handler is crashing... let's stop trying to read data from this async handler. Also add the exception to the list because we don't want it throwing out of this lambda + _outputDataReceivedExceptions.Add(ex); return true; } } - + return false; } private bool TimeoutExpired(DateTime startTime) @@ -281,66 +273,57 @@ private bool TimeoutExpired(DateTime startTime) } - public string GetFullPythonInstallDirectory() + public string GetFullPythonPath() { - return Path.Combine(Path.GetPathRoot(typeof(PythonDataProvider).Assembly.Location), GetPythonFolderName()); + return GetPython(Version == PythonVersion.Version2 ? '2' : '3').path; } - private string GetPythonFolderName() - { - switch (Version) - { - case PythonVersion.NotSet: - throw new Exception("Python version not set yet"); - case PythonVersion.Version2: - return "python27"; - case PythonVersion.Version3: - return "python35"; - default: - throw new ArgumentOutOfRangeException(); - } - } - - private string GetExpectedPythonVersion() - { - switch (Version) - { - case PythonVersion.NotSet: - throw new Exception("Python version not set yet"); - case PythonVersion.Version2: - return "2.7.1"; - case PythonVersion.Version3: - return "3.4.3"; - default: - throw new ArgumentOutOfRangeException(); - } - } - private string GetCompatiblePythonVersion() + [SupportedOSPlatform("windows")] + private static IEnumerable<(decimal minor, string fullVersion, string path)> GetPythonVersions(RegistryKey? k,char major) { - switch (Version) + if (k is null) yield break; + + foreach (var v in k.GetSubKeyNames()) { - case PythonVersion.NotSet: - throw new Exception("Python version not set yet"); - case PythonVersion.Version2: - return "2"; - case PythonVersion.Version3: - return "3"; - default: - throw new ArgumentOutOfRangeException(); + if (v.Length < 3 || v[0] != major || v[1] != '.' || !decimal.TryParse(v[2..], out var minor)) + continue; + + using var details = k.OpenSubKey(v); + if (details is null) continue; + + var fullVersion = details?.GetValue("Version") ?? v; + + using var pathKey = details?.OpenSubKey("InstallPath"); + if (pathKey is null) continue; + + var path = pathKey.GetValue("ExecutablePath")?.ToString() ?? Path.Combine(pathKey?.GetValue(null)?.ToString() ?? "DUMMY","python.exe"); + + if (!path.Contains("DUMMY",StringComparison.Ordinal)) + yield return (minor,fullVersion.ToString()??"0.0.0", path.ToString()??"none"); } } - public string GetDescription() - { - throw new NotImplementedException(); - } - public IDataProvider Clone() + private static (decimal minor, string fullVersion, string path) GetPython(char major) { - throw new NotImplementedException(); + if (!OperatingSystem.IsWindows()) throw new InvalidOperationException("This Python plugin is Windows only for now"); + + using var machine = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Python\\PythonCore"); + using var user = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Python\\PythonCore"); + using var machine32 = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Python\\PythonCore"); + using var user32 = Registry.CurrentUser.OpenSubKey("SOFTWARE\\WOW6432Node\\Python\\PythonCore"); + var candidate = GetPythonVersions(machine, major).Union(GetPythonVersions(user, major)).DefaultIfEmpty() + .MaxBy(static v => v.minor); + return candidate; } - public bool Validate(ILoadDirectory destination) + private string GetExpectedPythonVersion() { - return true; + if (Version != PythonVersion.Version2 && Version!=PythonVersion.Version3) + throw new Exception("Python version not set yet or invalid"); + + if (!OperatingSystem.IsWindows()) throw new InvalidOperationException("This Python plugin is Windows only for now"); + + var major = Version == PythonVersion.Version2 ? '2' : '3'; + return GetPython(major).fullVersion; } } \ No newline at end of file diff --git a/Python/LoadModules.Extensions.Python/LoadModules.Extensions.Python.csproj b/Python/LoadModules.Extensions.Python/LoadModules.Extensions.Python.csproj index 1f0dd5e..74283e4 100644 --- a/Python/LoadModules.Extensions.Python/LoadModules.Extensions.Python.csproj +++ b/Python/LoadModules.Extensions.Python/LoadModules.Extensions.Python.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 ..\..\ false LoadModules.Extensions @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/Python/LoadModules.Extensions.Python/Properties/AssemblyInfo.cs b/Python/LoadModules.Extensions.Python/Properties/AssemblyInfo.cs index 527c238..baf104b 100644 --- a/Python/LoadModules.Extensions.Python/Properties/AssemblyInfo.cs +++ b/Python/LoadModules.Extensions.Python/Properties/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] diff --git a/RDMP b/RDMP new file mode 160000 index 0000000..f696da6 --- /dev/null +++ b/RDMP @@ -0,0 +1 @@ +Subproject commit f696da681d442863c7077dbac1ec4ecc634e0dca diff --git a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/LoadModules.Extensions.ReleasePlugins.csproj b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/LoadModules.Extensions.ReleasePlugins.csproj index 1d9f1d8..6e9d915 100644 --- a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/LoadModules.Extensions.ReleasePlugins.csproj +++ b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/LoadModules.Extensions.ReleasePlugins.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 true LoadModules.Extensions.ReleasePlugins LoadModules.Extensions.ReleasePlugins @@ -29,9 +29,9 @@ - - - + + + \ No newline at end of file diff --git a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/NotifyEventArgsProxy.cs b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/NotifyEventArgsProxy.cs index 4dde4a0..d67f98d 100644 --- a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/NotifyEventArgsProxy.cs +++ b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/NotifyEventArgsProxy.cs @@ -1,11 +1,10 @@ -using System; -using Rdmp.Core.ReusableLibraryCode.Progress; +using Rdmp.Core.ReusableLibraryCode.Progress; namespace LoadModules.Extensions.ReleasePlugins.Data; public class NotifyEventArgsProxy : NotifyEventArgs { - public NotifyEventArgsProxy() : base(ProgressEventType.Information, String.Empty, null) + public NotifyEventArgsProxy() : base(ProgressEventType.Information, string.Empty, null) { } } \ No newline at end of file diff --git a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPDataReleaseDestination.cs b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPDataReleaseDestination.cs index 771bc4f..99c24fd 100644 --- a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPDataReleaseDestination.cs +++ b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPDataReleaseDestination.cs @@ -17,7 +17,7 @@ namespace LoadModules.Extensions.ReleasePlugins; public class RemoteRDMPDataReleaseDestination : IPluginDataFlowComponent, IDataFlowDestination, IPipelineRequirement, IPipelineRequirement { - [DemandsNestedInitialization()] + [DemandsNestedInitialization] public RemoteRDMPReleaseEngineSettings RDMPReleaseSettings { get; set; } private RemoteRDMPReleaseEngine _remoteRDMPReleaseEngineengine; @@ -35,30 +35,29 @@ public ReleaseAudit ProcessPipelineData(ReleaseAudit releaseAudit, IDataLoadEven releaseAudit.ReleaseFolder = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))); if (!releaseAudit.ReleaseFolder.Exists) releaseAudit.ReleaseFolder.Create(); - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "No destination folder specified! Did you forget to introduce and initialize the ReleaseFolderProvider in the pipeline? " + - "The release output will be located in " + releaseAudit.ReleaseFolder.FullName)); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, + $"No destination folder specified! Did you forget to introduce and initialize the ReleaseFolderProvider in the pipeline? The release output will be located in {releaseAudit.ReleaseFolder.FullName}")); } if (_releaseData.ReleaseState == ReleaseState.DoingPatch) { listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "CumulativeExtractionResults for datasets not included in the Patch will now be erased.")); - int recordsDeleted = 0; + var recordsDeleted = 0; - foreach (var configuration in _releaseData.ConfigurationsForRelease.Keys) + foreach (var redundantResult in _releaseData.ConfigurationsForRelease.Keys + .Select(configuration => new { configuration, current = configuration }) + .Select(t => new { t, currentResults = t.configuration.CumulativeExtractionResults }) + .SelectMany(t => t.currentResults.Where(r => + _releaseData.ConfigurationsForRelease[t.t.current] + .All(rp => rp.DataSet.ID != r.ExtractableDataSet_ID)))) { - IExtractionConfiguration current = configuration; - var currentResults = configuration.CumulativeExtractionResults; - - //foreach existing CumulativeExtractionResults if it is not included in the patch then it should be deleted - foreach (var redundantResult in currentResults.Where(r => _releaseData.ConfigurationsForRelease[current].All(rp => rp.DataSet.ID != r.ExtractableDataSet_ID))) - { - redundantResult.DeleteInDatabase(); - recordsDeleted++; - } + redundantResult.DeleteInDatabase(); + recordsDeleted++; } - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Deleted " + recordsDeleted + " old CumulativeExtractionResults (That were not included in the final Patch you are preparing)")); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Deleted {recordsDeleted} old CumulativeExtractionResults (That were not included in the final Patch you are preparing)")); } _remoteRDMPReleaseEngineengine = new RemoteRDMPReleaseEngine(_project, RDMPReleaseSettings, listener, releaseAudit.ReleaseFolder); @@ -75,17 +74,18 @@ public void Dispose(IDataLoadEventListener listener, Exception pipelineFailureEx { try { - int remnantsDeleted = 0; + var remnantsDeleted = 0; - foreach (ExtractionConfiguration configuration in _releaseData.ConfigurationsForRelease.Keys) - foreach (IReleaseLog remnant in configuration.ReleaseLog) + foreach (var remnant in _releaseData.ConfigurationsForRelease.Keys.Cast() + .SelectMany(configuration => configuration.ReleaseLog)) { remnant.DeleteInDatabase(); remnantsDeleted++; } if (remnantsDeleted > 0) - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Because release failed we are deleting ReleaseLogEntries, this resulted in " + remnantsDeleted + " deleted records, you will likely need to re-extract these datasets")); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Because release failed we are deleting ReleaseLogEntries, this resulted in {remnantsDeleted} deleted records, you will likely need to re-extract these datasets")); } catch (Exception e1) { @@ -96,7 +96,8 @@ public void Dispose(IDataLoadEventListener listener, Exception pipelineFailureEx if (pipelineFailureExceptionIfAny == null) { - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Data release succeded into: " + RDMPReleaseSettings.RemoteRDMP.Name)); + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Data release succeeded into: {RDMPReleaseSettings.RemoteRDMP.Name}")); // we can freeze the configuration now: foreach (var config in _configurationReleased) @@ -122,27 +123,23 @@ public void Abort(IDataLoadEventListener listener) public void Check(ICheckNotifier notifier) { var projectSafeHavenFolder = GetSafeHavenFolder(_project.MasterTicket); - if (string.IsNullOrWhiteSpace(projectSafeHavenFolder)) - notifier.OnCheckPerformed(new CheckEventArgs("No Safe Haven folder specified in the Project Master Ticket", CheckResult.Fail)); - else - notifier.OnCheckPerformed(new CheckEventArgs("Project Master Ticket contains Safe Haven folder", CheckResult.Success)); - + notifier.OnCheckPerformed(string.IsNullOrWhiteSpace(projectSafeHavenFolder) + ? new CheckEventArgs("No Safe Haven folder specified in the Project Master Ticket", CheckResult.Fail) + : new CheckEventArgs("Project Master Ticket contains Safe Haven folder", CheckResult.Success)); + ((ICheckable)RDMPReleaseSettings).Check(notifier); } private string GetSafeHavenFolder(string masterTicket) { - if (String.IsNullOrWhiteSpace(masterTicket)) - return "Proj-" + _project.ProjectNumber; + if (string.IsNullOrWhiteSpace(masterTicket)) + return $"Proj-{_project.ProjectNumber}"; var catalogueRepository = _project.DataExportRepository.CatalogueRepository; var factory = new TicketingSystemFactory(catalogueRepository); var system = factory.CreateIfExists(catalogueRepository.GetTicketingSystem()); - if (system == null) - return String.Empty; - - return system.GetProjectFolderName(masterTicket).Replace("/", ""); + return system?.GetProjectFolderName(masterTicket).Replace("/", "") ?? string.Empty; } public void PreInitialize(Project value, IDataLoadEventListener listener) diff --git a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngine.cs b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngine.cs index 59dd627..6fe9008 100644 --- a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngine.cs +++ b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngine.cs @@ -17,13 +17,12 @@ namespace LoadModules.Extensions.ReleasePlugins; public class RemoteRDMPReleaseEngine : ReleaseEngine { - public RemoteRDMPReleaseEngineSettings RemoteRDMPSettings { get; set; } + public RemoteRDMPReleaseEngineSettings RemoteRDMPSettings { get; } - public RemoteRDMPReleaseEngine(Project project, RemoteRDMPReleaseEngineSettings releaseSettings, IDataLoadEventListener listener, DirectoryInfo releaseFolder) : base(project, new ReleaseEngineSettings(), listener, new ReleaseAudit() { ReleaseFolder = releaseFolder }) + public RemoteRDMPReleaseEngine(Project project, RemoteRDMPReleaseEngineSettings releaseSettings, IDataLoadEventListener listener, DirectoryInfo releaseFolder) : base(project, new ReleaseEngineSettings(), listener, new ReleaseAudit { ReleaseFolder = releaseFolder }) { RemoteRDMPSettings = releaseSettings; - if (RemoteRDMPSettings == null) - RemoteRDMPSettings = new RemoteRDMPReleaseEngineSettings(); + RemoteRDMPSettings ??= new RemoteRDMPReleaseEngineSettings(); } public override void DoRelease(Dictionary> toRelease, Dictionary environments, bool isPatch) @@ -35,7 +34,7 @@ public override void DoRelease(Dictionary messages; + if (!result.IsSuccessStatusCode) { - var result = client.PostAsync(RemoteRDMPSettings.RemoteRDMP.GetUrlForRelease(), content).Result; - string resultStream; - List messages; - if (!result.IsSuccessStatusCode) - { - resultStream = result.Content.ReadAsStringAsync().Result; - messages = JsonConvert.DeserializeObject>(resultStream); - foreach (var eventArg in messages) - { - _listener.OnNotify(this, eventArg); - } - throw new Exception("Upload failed"); - } - else + resultStream = result.Content.ReadAsStringAsync().Result; + messages = JsonConvert.DeserializeObject>(resultStream); + foreach (var eventArg in messages) { - resultStream = result.Content.ReadAsStringAsync().Result; - messages = JsonConvert.DeserializeObject>(resultStream); - foreach (var eventArg in messages) - { - _listener.OnNotify(this, eventArg); - } - _listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Upload succeeded")); + _listener.OnNotify(this, eventArg); } + throw new Exception("Upload failed"); } - catch (Exception ex) + else { - _listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, "Failed to upload data", ex)); - throw; + resultStream = result.Content.ReadAsStringAsync().Result; + messages = JsonConvert.DeserializeObject>(resultStream); + foreach (var eventArg in messages) + { + _listener.OnNotify(this, eventArg); + } + _listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "Upload succeeded")); } } + catch (Exception ex) + { + _listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, "Failed to upload data", ex)); + throw; + } } private void ZipReleaseFolder(DirectoryInfo customExtractionDirectory, string zipPassword, string zipOutput) { var zip = new ZipFile() { UseZip64WhenSaving = Zip64Option.AsNecessary }; - if (!String.IsNullOrWhiteSpace(zipPassword)) + if (!string.IsNullOrWhiteSpace(zipPassword)) zip.Password = zipPassword; zip.AddDirectory(customExtractionDirectory.FullName); @@ -114,23 +111,18 @@ private void ZipReleaseFolder(DirectoryInfo customExtractionDirectory, string zi private string GetArchiveNameForProject() { - var prefix = DateTime.UtcNow.ToString("yyyy-MM-dd_"); - var nameToUse = "Proj-" + Project.ProjectNumber; - return prefix + "Release-" + nameToUse; + return $"{DateTime.UtcNow:yyyy-MM-dd_}Release-Proj-{Project.ProjectNumber}"; } private string GetSafeHavenFolder(string masterTicket) { - if (String.IsNullOrWhiteSpace(masterTicket)) - return "Proj-" + Project.ProjectNumber; + if (string.IsNullOrWhiteSpace(masterTicket)) + return $"Proj-{Project.ProjectNumber}"; var catalogueRepository = Project.DataExportRepository.CatalogueRepository; var factory = new TicketingSystemFactory(catalogueRepository); var system = factory.CreateIfExists(catalogueRepository.GetTicketingSystem()); - if (system == null) - return String.Empty; - - return system.GetProjectFolderName(masterTicket).Replace("/", ""); + return system == null ? string.Empty : system.GetProjectFolderName(masterTicket).Replace("/", ""); } } \ No newline at end of file diff --git a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngineSettings.cs b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngineSettings.cs index 7031339..3567c69 100644 --- a/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngineSettings.cs +++ b/ReleasePlugins/LoadModules.Extensions.ReleasePlugins/RemoteRDMPReleaseEngineSettings.cs @@ -12,7 +12,7 @@ public class RemoteRDMPReleaseEngineSettings : ICheckable [DemandsInitialization("Password for ZIP package")] public EncryptedString ZipPassword { get; set; } - [DemandsInitialization("Delete the released files from the origin location if release is succesful", DefaultValue = true)] + [DemandsInitialization("Delete the released files from the origin location if release is successful", DefaultValue = true)] public bool DeleteFilesOnSuccess { get; set; } [DemandsInitialization("Remote RDMP instance")] @@ -29,19 +29,19 @@ public void Check(ICheckNotifier notifier) { Credentials = new NetworkCredential { - UserName = this.RemoteRDMP.Username, - Password = this.RemoteRDMP.GetDecryptedPassword() + UserName = RemoteRDMP.Username, + Password = RemoteRDMP.GetDecryptedPassword() } }; var client = new HttpClient(handler); try { - var baseUri = new UriBuilder(new Uri(this.RemoteRDMP.URL)); + var baseUri = new UriBuilder(new Uri(RemoteRDMP.URL)); baseUri.Path += "/api/plugin/"; var message = new HttpRequestMessage(HttpMethod.Head, baseUri.ToString()); var check = client.SendAsync(message).Result; check.EnsureSuccessStatusCode(); - notifier.OnCheckPerformed(new CheckEventArgs("Checks passed " + check.Content.ReadAsStringAsync().Result, CheckResult.Success)); + notifier.OnCheckPerformed(new CheckEventArgs($"Checks passed {check.Content.ReadAsStringAsync().Result}", CheckResult.Success)); } catch (Exception e) { diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs index ac26441..c8f67ae 100644 --- a/SharedAssemblyInfo.cs +++ b/SharedAssemblyInfo.cs @@ -1,4 +1,7 @@ using System.Reflection; +#if WINDOWS +[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] +#endif [assembly: AssemblyCompany("Health Informatics Centre, University of Dundee")] [assembly: AssemblyProduct("Extensions")] @@ -6,6 +9,6 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("6.1.0")] -[assembly: AssemblyFileVersion("6.1.0")] -[assembly: AssemblyInformationalVersion("6.1.0")] +[assembly: AssemblyVersion("6.2.0")] +[assembly: AssemblyFileVersion("6.2.0")] +[assembly: AssemblyInformationalVersion("6.2.0-rc1")] diff --git a/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/RStudioAttacher.cs b/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/RStudioAttacher.cs index ecaa3d6..2eeea8d 100644 --- a/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/RStudioAttacher.cs +++ b/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/RStudioAttacher.cs @@ -42,17 +42,21 @@ public override void Check(ICheckNotifier notifier) try { if (!RscriptRootDirectory.Exists) - throw new DirectoryNotFoundException("The specified Rscript root directory: " + RscriptRootDirectory.FullName + " does not exist"); + throw new DirectoryNotFoundException( + $"The specified Rscript root directory: {RscriptRootDirectory.FullName} does not exist"); var fullPathToRscriptExe = Path.Combine(RscriptRootDirectory.FullName, "Rscript.exe"); if (!File.Exists(fullPathToRscriptExe)) - throw new FileNotFoundException("The specified Rscript root directory: " + RscriptRootDirectory.FullName + " does not contain Rscript.exe"); + throw new FileNotFoundException( + $"The specified Rscript root directory: {RscriptRootDirectory.FullName} does not contain Rscript.exe"); if (!FullPathToRScript.Exists) - throw new FileNotFoundException("The specified R script to run: " + FullPathToRScript.FullName + " does not exist"); + throw new FileNotFoundException( + $"The specified R script to run: {FullPathToRScript.FullName} does not exist"); if (!OutputDirectory.Exists) - throw new DirectoryNotFoundException("The specified output directory: " + OutputDirectory.FullName + " does not exist"); + throw new DirectoryNotFoundException( + $"The specified output directory: {OutputDirectory.FullName} does not exist"); } catch (Exception e) { @@ -79,7 +83,8 @@ public override ExitCodeType Attach(IDataLoadJob job, GracefulCancellationToken return ExitCodeType.Error; } - job.OnNotify(this, new NotifyEventArgs(exitCode == 0 ? ProgressEventType.Information : ProgressEventType.Error, "R script terminated with exit code " + exitCode)); + job.OnNotify(this, new NotifyEventArgs(exitCode == 0 ? ProgressEventType.Information : ProgressEventType.Error, + $"R script terminated with exit code {exitCode}")); return exitCode == 0 ? ExitCodeType.Success : ExitCodeType.Error; } @@ -96,16 +101,20 @@ private int ExecuteProcess(ProcessStartInfo processStartInfo, int scriptTimeout, Process p; try { - p = new Process(); - p.StartInfo = processStartInfo; + p = new Process + { + StartInfo = processStartInfo + }; - job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "commandline: " + processStartInfo.Arguments)); + job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"commandline: {processStartInfo.Arguments}")); p.Start(); } catch (Exception e) { - throw new Exception("Failed to launch:" + Environment.NewLine + processStartInfo.FileName + Environment.NewLine + " with Arguments:" + processStartInfo.Arguments, e); + throw new Exception( + $"Failed to launch:{Environment.NewLine}{processStartInfo.FileName}{Environment.NewLine} with Arguments:{processStartInfo.Arguments}", e); } var startTime = DateTime.Now; @@ -124,13 +133,14 @@ private int ExecuteProcess(ProcessStartInfo processStartInfo, int scriptTimeout, killed = false; } - throw new TimeoutException("Process command " + processStartInfo.FileName + " with arguments " + processStartInfo.Arguments + " did not complete after " + scriptTimeout + " seconds " + (killed ? "(After timeout we killed the process successfully)" : "(We also failed to kill the process after the timeout expired)")); + throw new TimeoutException( + $"Process command {processStartInfo.FileName} with arguments {processStartInfo.Arguments} did not complete after {scriptTimeout} seconds {(killed ? "(After timeout we killed the process successfully)" : "(We also failed to kill the process after the timeout expired)")}"); } } var errors = p.StandardError.ReadToEnd(); - if (!String.IsNullOrEmpty(errors)) - job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, "Error from R: " + errors)); + if (!string.IsNullOrEmpty(errors)) + job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, $"Error from R: {errors}")); return p.ExitCode; } @@ -149,39 +159,37 @@ private ProcessStartInfo CreateCommand() var actualOutputDir = CreateActualOutputDir(scriptFileName); var rscriptFullPath = Path.Combine(RscriptRootDirectory.FullName, "Rscript.exe"); - var fullPrintPath = Path.Combine(actualOutputDir, scriptFileName + ".Rout"); + var fullPrintPath = Path.Combine(actualOutputDir, $"{scriptFileName}.Rout"); //var fullLogPath = Path.Combine(actualOutputDir, scriptFileName + ".log"); //var dataInConnection = GetConnectionString(InputDatabase); //var dataOutConnection = GetConnectionString(_dbInfo); - var command = "--vanilla --default-packages=" + DefaultPackages + - " \"" + FullPathToRScript.FullName.Replace('\\', '/') + "\"" + - " " + InputDatabase.Server + " " + InputDatabase.Database + - " " + _dbInfo.Server + " " + _dbInfo.GetRuntimeName() + - " \"" + actualOutputDir.TrimEnd('\\').Replace('\\','/') + "/\"" + - " >\"" + fullPrintPath.Replace('\\', '/') + "\""; + var command = + $"--vanilla --default-packages={DefaultPackages} \"{FullPathToRScript.FullName.Replace('\\', '/')}\" {InputDatabase.Server} {InputDatabase.Database} {_dbInfo.Server} {_dbInfo.GetRuntimeName()} \"{actualOutputDir.TrimEnd('\\').Replace('\\', '/')}/\" >\"{fullPrintPath.Replace('\\', '/')}\""; - var info = new ProcessStartInfo(rscriptFullPath); - info.Arguments = command; + var info = new ProcessStartInfo(rscriptFullPath) + { + Arguments = command + }; return info; } private string GetConnectionString(DiscoveredDatabase db) { - return String.Format("Server={0};Database={1};IntegratedSecurity=true;DRIVER=SQL Server", db.Server.Name, db.GetRuntimeName()); + return $"Server={db.Server.Name};Database={db.GetRuntimeName()};IntegratedSecurity=true;DRIVER=SQL Server"; } private string GetConnectionString(ExternalDatabaseServer db) { - return String.Format("Server={0};Database={1};IntegratedSecurity=true;DRIVER=SQL Server", db.Server, db.Database); + return $"Server={db.Server};Database={db.Database};IntegratedSecurity=true;DRIVER=SQL Server"; } private string CreateActualOutputDir(string scriptFileName) { var timeStampString = DateTime.Now.ToString("yyyyMMddTHHmmss"); - var dir = Path.Combine(OutputDirectory.FullName, timeStampString + "_" + scriptFileName); + var dir = Path.Combine(OutputDirectory.FullName, $"{timeStampString}_{scriptFileName}"); try { diff --git a/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/SasAttacher.cs b/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/SasAttacher.cs index 1c5ef88..0902dda 100644 --- a/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/SasAttacher.cs +++ b/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/Attachers/SasAttacher.cs @@ -38,17 +38,21 @@ public override void Check(ICheckNotifier notifier) try { if (!SASRootDirectory.Exists) - throw new DirectoryNotFoundException("The specified SAS root directory: " + SASRootDirectory.FullName + " does not exist"); + throw new DirectoryNotFoundException( + $"The specified SAS root directory: {SASRootDirectory.FullName} does not exist"); var fullPathToSasExe = Path.Combine(SASRootDirectory.FullName, "sas.exe"); if (!File.Exists(fullPathToSasExe)) - throw new FileNotFoundException("The specified SAS root directory: " + SASRootDirectory.FullName + " does not contain sas.exe"); + throw new FileNotFoundException( + $"The specified SAS root directory: {SASRootDirectory.FullName} does not contain sas.exe"); if (!FullPathToSASScript.Exists) - throw new FileNotFoundException("The specified SAS script to run: " + FullPathToSASScript.FullName + " does not exist"); + throw new FileNotFoundException( + $"The specified SAS script to run: {FullPathToSASScript.FullName} does not exist"); if (!OutputDirectory.Exists) - throw new DirectoryNotFoundException("The specified output directory: " + OutputDirectory.FullName + " does not exist"); + throw new DirectoryNotFoundException( + $"The specified output directory: {OutputDirectory.FullName} does not exist"); } catch (Exception e) { @@ -75,7 +79,8 @@ public override ExitCodeType Attach(IDataLoadJob job, GracefulCancellationToken return ExitCodeType.Error; } - job.OnNotify(this, new NotifyEventArgs(exitCode == 0 ? ProgressEventType.Information : ProgressEventType.Error, "SAS script terminated with exit code " + exitCode)); + job.OnNotify(this, new NotifyEventArgs(exitCode == 0 ? ProgressEventType.Information : ProgressEventType.Error, + $"SAS script terminated with exit code {exitCode}")); return exitCode == 0 ? ExitCodeType.Success : ExitCodeType.Error; } @@ -90,16 +95,20 @@ private int ExecuteProcess(ProcessStartInfo processStartInfo, int scriptTimeout, Process p; try { - p = new Process(); - p.StartInfo = processStartInfo; + p = new Process + { + StartInfo = processStartInfo + }; - job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, "commandline: " + processStartInfo.Arguments)); + job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"commandline: {processStartInfo.Arguments}")); p.Start(); } catch (Exception e) { - throw new Exception("Failed to launch:" + Environment.NewLine + processStartInfo.FileName + Environment.NewLine + " with Arguments:" + processStartInfo.Arguments, e); + throw new Exception( + $"Failed to launch:{Environment.NewLine}{processStartInfo.FileName}{Environment.NewLine} with Arguments:{processStartInfo.Arguments}", e); } var startTime = DateTime.Now; @@ -118,7 +127,8 @@ private int ExecuteProcess(ProcessStartInfo processStartInfo, int scriptTimeout, killed = false; } - throw new TimeoutException("Process command " + processStartInfo.FileName + " with arguments " + processStartInfo.Arguments + " did not complete after " + scriptTimeout + " seconds " + (killed ? "(After timeout we killed the process successfully)" : "(We also failed to kill the process after the timeout expired)")); + throw new TimeoutException( + $"Process command {processStartInfo.FileName} with arguments {processStartInfo.Arguments} did not complete after {scriptTimeout} seconds {(killed ? "(After timeout we killed the process successfully)" : "(We also failed to kill the process after the timeout expired)")}"); } } @@ -139,40 +149,37 @@ private ProcessStartInfo CreateCommand() var actualOutputDir = CreateActualOutputDir(scriptFileName); var sasFullPath = Path.Combine(SASRootDirectory.FullName, "sas.exe"); - var fullPrintPath = Path.Combine(actualOutputDir, scriptFileName + ".out"); - var fullLogPath = Path.Combine(actualOutputDir, scriptFileName + ".log"); + var fullPrintPath = Path.Combine(actualOutputDir, $"{scriptFileName}.out"); + var fullLogPath = Path.Combine(actualOutputDir, $"{scriptFileName}.log"); var dataInConnection = GetSASConnectionString(InputDatabase); var dataOutConnection = GetSASConnectionString(_dbInfo); - var command = "-set output \"" + actualOutputDir + "\"" + - " -set connect \"" + dataInConnection + "\"" + - " -set connectout \"" + dataOutConnection + "\"" + - " -sysin \"" + FullPathToSASScript.FullName + "\"" + - " -nosplash -noterminal -nostatuswin -noicon" + - " -print \"" + fullPrintPath + "\"" + - " -log \"" + fullLogPath + "\""; + var command = + $"-set output \"{actualOutputDir}\" -set connect \"{dataInConnection}\" -set connectout \"{dataOutConnection}\" -sysin \"{FullPathToSASScript.FullName}\" -nosplash -noterminal -nostatuswin -noicon -print \"{fullPrintPath}\" -log \"{fullLogPath}\""; - var info = new ProcessStartInfo(sasFullPath); - info.Arguments = command; + var info = new ProcessStartInfo(sasFullPath) + { + Arguments = command + }; return info; } private string GetSASConnectionString(DiscoveredDatabase db) { - return String.Format("Server={0};Database={1};IntegratedSecurity=true;DRIVER=SQL Server", db.Server.Name, db.GetRuntimeName()); + return $"Server={db.Server.Name};Database={db.GetRuntimeName()};IntegratedSecurity=true;DRIVER=SQL Server"; } private string GetSASConnectionString(ExternalDatabaseServer db) { - return String.Format("Server={0};Database={1};IntegratedSecurity=true;DRIVER=SQL Server", db.Server, db.Database); + return $"Server={db.Server};Database={db.Database};IntegratedSecurity=true;DRIVER=SQL Server"; } private string CreateActualOutputDir(string scriptFileName) { var timeStampString = DateTime.Now.ToString("yyyyMMddTHHmmss"); - var dir = Path.Combine(OutputDirectory.FullName, timeStampString + "_" + scriptFileName); + var dir = Path.Combine(OutputDirectory.FullName, $"{timeStampString}_{scriptFileName}"); try { diff --git a/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution.csproj b/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution.csproj index bd1be43..255158d 100644 --- a/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution.csproj +++ b/StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution/LoadModules.Extensions.StatsScriptsExecution.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 LoadModules.Extensions.StatsScriptsExecution LoadModules.Extensions.StatsScriptsExecution Copyright © 2018 @@ -10,6 +10,6 @@ - + \ No newline at end of file