From c3e1ce694ee3fb9661625800b73b3410a0eab5ae Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 27 Nov 2023 14:01:30 +0000 Subject: [PATCH 01/64] add chi column finder --- Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs | 462 ++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs diff --git a/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs b/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs new file mode 100644 index 0000000000..8a2f5eced9 --- /dev/null +++ b/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs @@ -0,0 +1,462 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.IO; +using System.Linq; +using System.Threading; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.DataExport.DataExtraction.Commands; +using Rdmp.Core.DataFlowPipeline; +using Rdmp.Core.DataFlowPipeline.Requirements; +using Rdmp.Core.ReusableLibraryCode.Annotations; +using Rdmp.Core.ReusableLibraryCode.Checks; +using Rdmp.Core.ReusableLibraryCode.Progress; +using YamlDotNet.Serialization; + +namespace Rdmp.Core.DataFlowPipeline; + +/// +/// Pipeline component designed to prevent DataTable columns containing CHIs passing through the pipeline. +/// +[Description("Crashes the pipeline if any columns are suspected of containing CHIs")] +public sealed partial class CHIColumnFinder : IPluginDataFlowComponent, IPipelineRequirement, IPipelineRequirement +{ + [DemandsInitialization("Component will be shut down until this date and time", DemandType = DemandType.Unspecified)] + public DateTime? OverrideUntil { get; set; } + + [DemandsInitialization("A Yaml file that outlines which columns in which catalogues can be safely ignored")] + public string AllowListFile { get; set; } + + private DirectoryInfo OutputFileDirectory; + + [DemandsInitialization("If non-zero, will stop searching for CHIs after it has found that many of them in the extraction.", DefaultValue = 0)] + + public int BailOutAfter { get; set; } + + [DemandsInitialization("If checked, will log a lot more information about the CHI finding process.")] + + public bool VerboseLogging { get; set; } = false; + + private bool _firstTime = true; + + private bool _isTableAlreadyNamed; + private readonly Dictionary> _allowLists = new(); + + private const string RdmpAll = "RDMP_ALL"; + private readonly string _potentialChiLocationFileDescriptor = "_Potential_CHI_Locations.csv"; + private readonly string _csvColumns = "Column,Potential CHI,Value"; + private IBasicActivateItems _activator; + + public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) + { + if (OverrideUntil.HasValue && OverrideUntil.Value > DateTime.Now) + { + if (_firstTime) + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"This component is still currently being overridden until the specified date: {OverrideUntil.Value:g}")); + _firstTime = false; + return toProcess; + } + + List columnGreenList = new(); + if (_allowLists.TryGetValue(RdmpAll, out var _extractionSpecificAllowances)) + columnGreenList.AddRange(_extractionSpecificAllowances); + if (_allowLists.TryGetValue(toProcess.TableName, out var _catalogueSpecificAllowances)) + columnGreenList.AddRange(_catalogueSpecificAllowances.ToList()); + + var count=0; + string fileLocation = null; + if (OutputFileDirectory?.Exists == true) + { + var CHIDir = Path.Combine(OutputFileDirectory.FullName, "FoundCHIs"); + if (!Directory.Exists(CHIDir)) Directory.CreateDirectory(CHIDir); + fileLocation = Path.Combine(CHIDir, $"{toProcess.TableName}{_potentialChiLocationFileDescriptor}"); + if (File.Exists(fileLocation) && BailOutAfter>0) + { + count = File.ReadLines(fileLocation).Count()-1; + if (count > BailOutAfter) + { + if (VerboseLogging) + listener.OnNotify(this, + new NotifyEventArgs(ProgressEventType.Information, + $"Have skipped this chunk of Catalogue {toProcess.TableName} as there is already a number of CHIs already found")); + return toProcess; + } + } + } + + var listFile = new Lazy(() => + { + var stream = fileLocation is not null ? File.AppendText(fileLocation) : null; + if (stream?.BaseStream.Length == 0) + stream.WriteLine(_csvColumns); + return stream; + }, + LazyThreadSafetyMode.ExecutionAndPublication); + + //give the data table the correct name + if (toProcess.ExtendedProperties.ContainsKey("ProperlyNamed") && toProcess.ExtendedProperties["ProperlyNamed"]?.Equals(true) == true) + _isTableAlreadyNamed = true; + + if (columnGreenList.Count != 0 && VerboseLogging) + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"You have chosen the following columns to be ignored: {string.Join(",", columnGreenList)}")); + + try + { + foreach (var col in toProcess.Columns.Cast().Where(c => !columnGreenList.Contains(c.ColumnName.Trim()))) + { + foreach (var val in toProcess.Rows.Cast().Select(DeRef).AsParallel().Where(ContainsValidChi)) + { + Interlocked.Increment(ref count); + if (BailOutAfter > 0 && count > BailOutAfter) break; + + listFile.Value?.WriteLine($"{col.ColumnName},{GetPotentialCHI(val)},{val}"); + if (VerboseLogging || string.IsNullOrWhiteSpace(fileLocation)) + { + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, + $"Column {col.ColumnName} in Dataset {toProcess.TableName} appears to contain a CHI ({val})")); + if (!_isTableAlreadyNamed) + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, + "DataTable has not been named. If you want to know the dataset that the error refers to please add an ExtractCatalogueMetadata to the extraction pipeline.")); + } + } + var countWritten = BailOutAfter > 0 ? Math.Min(count, BailOutAfter) : count; + if (count != 0 && VerboseLogging) listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, $"Have Written {countWritten} Potential CHIs to {fileLocation}")); + + continue; + + [NotNull] + string DeRef([NotNull] DataRow row) => row[col].ToString() ?? ""; + } + } + finally + { + if (listFile.IsValueCreated && listFile.Value is not null) + { + listFile.Value?.Flush(); + listFile.Value?.Dispose(); + } + } + + if (count>0 && OutputFileDirectory?.Exists == true) + { + var countWritten = BailOutAfter > 0 ? Math.Min(count, BailOutAfter) : count; + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, $"{countWritten} CHIs have been found in your extraction. Find them in {OutputFileDirectory.FullName}")); + if (_activator is not null) + { + toProcess.ExtendedProperties.Add("AlertUIAtEndOfProcess", new Tuple($"Some CHIs have been found in your extraction for the catalogue {toProcess.TableName}. Find them in {OutputFileDirectory.FullName}.",_activator)); + } + } + return toProcess; + } + + public void Dispose(IDataLoadEventListener listener, Exception pipelineFailureExceptionIfAny) + { + + } + + public void Abort(IDataLoadEventListener listener) + { + + } + + public void Check(ICheckNotifier notifier) + { + + } + + // True if date exists and checksum matches + private static bool ValidBits(int d, int m, int y, int c) + { + c %= 11; + if (c != 0) return false; + + return m switch + { + 1 or 3 or 5 or 7 or 8 or 10 or 12 => d is > 0 and < 32, + 4 or 6 or 9 or 11 => d is > 0 and < 31, + 2 => d is > 0 and < 29 || (d == 29 && y % 4 == 0), + _ => false, + }; + } + + private enum State + { + End, + Rest1, Rest2, Rest3, + Year1, Year2, + Month1, Month2, + Day1, Day2, + MaybeSpace, + Complete + } + + public static string GetPotentialCHI(string toCheckStr) + { + if (string.IsNullOrWhiteSpace(toCheckStr) || toCheckStr.Length < 9) return ""; + + var state = State.End; // Start of potential CHI + int day = 0, month = 0, year = 0, check = 0, indexOfDay = 0; + for (var i = toCheckStr.Length - 1; i >= 0; i--) + { + var c = toCheckStr[i]; + var digit = c - '0'; + if (digit is < 0 or > 9) + { + // Non-whitespace: if we're anywhere other than maybe-space, bail. + switch (state) + { + case State.MaybeSpace: + state = char.IsWhiteSpace(c) ? State.Year2 : State.End; + break; + + case State.Day1: // Might be a 9 digit "CHI" with leading zero removed + case State.Complete: + state = State.End; + if (ValidBits(day, month, year, check)) + return toCheckStr.Substring(i + 1, 9); + + break; + + default: + state = State.End; + break; + } + continue; + } + + // OK, we got a digit. What does it mean in the current state? + switch (state) + { + case State.End: + check = digit; + state = State.Rest3; + break; + + case State.Rest3: + check += digit * 2; + state = State.Rest2; + break; + + case State.Rest2: + check += digit * 3; + state = State.Rest1; + break; + + case State.Rest1: + check += digit * 4; + state = State.MaybeSpace; + break; + + case State.MaybeSpace: + case State.Year2: + check += digit * 5; + year = digit; + state = State.Year1; + break; + + case State.Year1: + check += digit * 6; + year += digit * 10; + state = State.Month2; + break; + + case State.Month2: + check += digit * 7; + month = digit; + state = State.Month1; + break; + + case State.Month1: + check += digit * 8; + month += digit * 10; + state = State.Day2; + break; + + case State.Day2: + check += digit * 9; + day = digit; + indexOfDay = i; + state = State.Day1; + break; + + case State.Day1: + check += digit * 10; + day += 10 * digit; + indexOfDay = i; + state = State.Complete; + break; + + case State.Complete: + // More than 10 digits - just keep consuming, cannot possibly be valid now. + day = 32; + break; + } + } + return ValidBits(day, month, year, check) ? toCheckStr.Substring(indexOfDay, Math.Min(10,toCheckStr.Length)) : ""; + } + + private static bool ContainsValidChi([CanBeNull] object toCheck) + { + if (toCheck == null || toCheck == DBNull.Value) + return false; + + var toCheckStr = toCheck.ToString(); + if (toCheckStr is null || toCheckStr.Length < 9) return false; + + var state = State.End; // Start of potential CHI + int day = 0, month = 0, year = 0, check = 0; + for (var i = toCheckStr.Length - 1; i >= 0; i--) + { + var c = toCheckStr[i]; + var digit = c - '0'; + if (digit is < 0 or > 9) + { + // Non-whitespace: if we're anywhere other than maybe-space, bail. + switch (state) + { + case State.MaybeSpace: + state = char.IsWhiteSpace(c) ? State.Year2 : State.End; + break; + + case State.Day1: // Might be a 9 digit "CHI" with leading zero removed + case State.Complete: + state = State.End; + if (ValidBits(day, month, year, check)) + return true; + + break; + + default: + state = State.End; + break; + } + continue; + } + + // OK, we got a digit. What does it mean in the current state? + switch (state) + { + case State.End: + check = digit; + state = State.Rest3; + break; + + case State.Rest3: + check += digit * 2; + state = State.Rest2; + break; + + case State.Rest2: + check += digit * 3; + state = State.Rest1; + break; + + case State.Rest1: + check += digit * 4; + state = State.MaybeSpace; + break; + + case State.MaybeSpace: + case State.Year2: + check += digit * 5; + year = digit; + state = State.Year1; + break; + + case State.Year1: + check += digit * 6; + year += digit * 10; + state = State.Month2; + break; + + case State.Month2: + check += digit * 7; + month = digit; + state = State.Month1; + break; + + case State.Month1: + check += digit * 8; + month += digit * 10; + state = State.Day2; + break; + + case State.Day2: + check += digit * 9; + day = digit; + state = State.Day1; + break; + + case State.Day1: + check += digit * 10; + day += 10 * digit; + state = State.Complete; + break; + + case State.Complete: + // More than 10 digits - just keep consuming, cannot possibly be valid now. + day = 32; + break; + } + } + return ValidBits(day, month, year, check); + } + + public void PreInitialize(IExtractCommand value, IDataLoadEventListener listener) + { + if (value is not ExtractDatasetCommand edcs) return; + + OutputFileDirectory = value.GetExtractionDirectory(); + try + { + var hashOnReleaseColumns = edcs.Catalogue.CatalogueItems.Select(static ci => ci.ExtractionInformation) + .Where(static ei => ei?.HashOnDataRelease == true).Select(static ei => ei.GetRuntimeName()).ToArray(); + + if (!hashOnReleaseColumns.Any() && string.IsNullOrWhiteSpace(AllowListFile)) return; + + if (hashOnReleaseColumns.Length > 0) + { + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"Ignoring the following columns as they have been hashed on release: {string.Join(", ", hashOnReleaseColumns)}")); + } + + if (File.Exists(AllowListFile) && _allowLists.Count == 0) + { + var allowListFileContent = File.ReadAllText(AllowListFile); + var deserializer = new DeserializerBuilder().Build(); + var yamlObject = deserializer.Deserialize>>(allowListFileContent); + foreach (var (catalogue, columns) in yamlObject) + { + _allowLists.Add(catalogue, columns); + } + } + + if (!hashOnReleaseColumns.Any()) return; + + if (_allowLists.TryGetValue("RDMP_ALL", out var allowAllList)) + { + allowAllList.AddRange(hashOnReleaseColumns); + _allowLists["RDMP_ALL"] = allowAllList; + } + else + { + _allowLists.Add("RDMP_ALL", hashOnReleaseColumns.ToList()); + } + } + catch (Exception e) + { + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, + $"Failed to get HashOnDataRelease columns for catalogue {edcs.Catalogue.Name} with the exception: {e.Message} Columns can be ignored manually using the Ignore Columns option", e)); + } + } + + public void PreInitialize(IBasicActivateItems value, IDataLoadEventListener listener) + { + _activator = value; + } + +} \ No newline at end of file From aa43931dfbc3a2c72bb2243d2488d54ee60effbc Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 27 Nov 2023 15:04:41 +0000 Subject: [PATCH 02/64] basic redaction commands --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 121 ++++++++++++++++++ .../ExecuteCommandRedactCHIsFromCatalogue.cs | 102 +++++++++++++++ Rdmp.Core/Curation/Data/RedactedCHI.cs | 100 +++++++++++++++ .../up/078_add_chi_redaction.sql | 17 +++ Rdmp.Core/Rdmp.Core.csproj | 10 +- 5 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs create mode 100644 Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs create mode 100644 Rdmp.Core/Curation/Data/RedactedCHI.cs create mode 100644 Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs new file mode 100644 index 0000000000..530d0e7dda --- /dev/null +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -0,0 +1,121 @@ + +using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.DataFlowPipeline; +using Rdmp.Core.ReusableLibraryCode.DataAccess; +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.Serialization; + +namespace Rdmp.Core.CommandExecution.AtomicCommands; + +public class ExecuteCommandIdentifyCHIInCatalogue : BasicCommandExecution, IAtomicCommand +{ + + private Catalogue _catalouge; + private IBasicActivateItems _activator; + private bool _bailOutEarly; + private readonly Dictionary> _allowLists = new(); + + + public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] Catalogue catalogue, bool bailOutEarly = false, string allowListLocation = null) : base(activator) + { + _catalouge = catalogue; + _activator = activator; + _bailOutEarly = bailOutEarly; + if(allowListLocation != null) + { + var allowListFileContent = File.ReadAllText(allowListLocation); + var deserializer = new DeserializerBuilder().Build(); + var yamlObject = deserializer.Deserialize>>(allowListFileContent); + foreach (var (cat, columns) in yamlObject) + { + _allowLists.Add(cat, columns); + } + } + } + + + public static string WrapCHIInContext(string chi, string source, int padding = 25) + { + var foundIndex = source.IndexOf(chi); + return $"{source[Math.Max(0, foundIndex - padding)..foundIndex]}{chi}{source[(foundIndex + chi.Length)..Math.Min(foundIndex + chi.Length + padding, source.Length)]}"; + } + + + + private void handleFoundCHI(string foundChi,string contextValue, string columnName) + { + if(foundChis.Rows.Count == 0) + { + //init + foundChis.Columns.Add("Potential CHI"); + foundChis.Columns.Add("Context"); + foundChis.Columns.Add("Source Column Name"); + } + var shrunkContext = WrapCHIInContext(foundChi,contextValue); + foundChis.Rows.Add(foundChi, shrunkContext, columnName); + } + private DataTable foundChis = new(); + + public override void Execute() + { + base.Execute(); + List columnAllowList = new(); + if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) + columnAllowList.AddRange(_extractionSpecificAllowances); + if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) + columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); + foreach (var item in _catalouge.CatalogueItems) + { + if (columnAllowList.Contains(item.Name)) continue; + + if (_bailOutEarly && foundChis.Rows.Count > 0) + { + break; + } + var column = item.ColumnInfo.Name; + int idxOfLastSplit = column.LastIndexOf('.'); + var columnName = column[(idxOfLastSplit + 1)..]; + var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); + var sql = $"SELECT {columnName} from {column[..idxOfLastSplit]}"; + var dt = new DataTable(); + dt.BeginLoadData(); + using (var cmd = server.GetCommand(sql, server.GetConnection())) + { + using var da = server.GetDataAdapter(cmd); + da.Fill(dt); + } + dt.EndLoadData(); + foreach (DataRow row in dt.Rows) + { + + var value = row[dt.Columns[0].ColumnName].ToString(); + var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); + if (!string.IsNullOrWhiteSpace(potentialCHI)) + { + handleFoundCHI(potentialCHI, value, item.Name); + if (_bailOutEarly) + { + break; + } + } + + + } + } + Console.WriteLine($"Found {foundChis.Rows.Count} CHIs in the {_catalouge.Name} Catalogue."); + foreach(DataRow row in foundChis.Rows) + { + Console.WriteLine($"{row["potential CHI"]} | {row["Context"]} | {row["Source Column Name"]}"); + + } + } +} diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs new file mode 100644 index 0000000000..514c7fda9a --- /dev/null +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -0,0 +1,102 @@ +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.CommandExecution; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Rdmp.Core.Curation.Data; +using YamlDotNet.Serialization; +using System.IO; +using Rdmp.Core.ReusableLibraryCode.DataAccess; +using System.Data; +using static NPOI.HSSF.Util.HSSFColor; +using Rdmp.Core.Curation.Data.Defaults; +using TB.ComponentModel; +using FAnsi.Discovery.TableCreation; +using HICPlugin.Curation.Data; +using Rdmp.Core.DataFlowPipeline; + +namespace Rdmp.Core.CommandExecution.AtomicCommands; + +public class ExecuteCommandRedactCHIsFromCatalogue : BasicCommandExecution, IAtomicCommand +{ + + private Catalogue _catalouge; + private IBasicActivateItems _activator; + private readonly Dictionary> _allowLists = new(); + + + public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] Catalogue catalogue, string allowListLocation = null) : base(activator) + { + _catalouge = catalogue; + _activator = activator; + if (allowListLocation != null) + { + var allowListFileContent = File.ReadAllText(allowListLocation); + var deserializer = new DeserializerBuilder().Build(); + var yamlObject = deserializer.Deserialize>>(allowListFileContent); + foreach (var (cat, columns) in yamlObject) + { + _allowLists.Add(cat, columns); + } + } + } + private void handleFoundCHI(string foundChi, string table, string column, string columnValue) + { + Console.WriteLine("Found CHI!"); + var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi,columnValue,20),$"{table}.{column}"); + rc.SaveToDatabase(); + var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + //TODO can be smarted about how we wrote tothe db, can share a connection etc + var sql = $"UPDATE {table} SET {column}='{redactedValue}' where {column}='{columnValue}'"; + var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); + var conn = server.GetConnection(); + conn.Open(); + using (var cmd = server.GetCommand(sql, conn)) + { + cmd.ExecuteNonQuery(); + conn.Close(); + } + } + + + public override void Execute() + { + base.Execute(); + List columnAllowList = new(); + if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) + columnAllowList.AddRange(_extractionSpecificAllowances); + if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) + columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); + foreach (var item in _catalouge.CatalogueItems) + { + if (columnAllowList.Contains(item.Name)) continue; + + var column = item.ColumnInfo.Name; + int idxOfLastSplit = column.LastIndexOf('.'); + string table = column[..idxOfLastSplit]; + var columnName = column[(idxOfLastSplit + 1)..]; + var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); + var sql = $"SELECT {columnName} from {table}"; + var dt = new DataTable(); + dt.BeginLoadData(); + using (var cmd = server.GetCommand(sql, server.GetConnection())) + { + using var da = server.GetDataAdapter(cmd); + da.Fill(dt); + } + dt.EndLoadData(); + foreach (DataRow row in dt.Rows) + { + + var value = row[dt.Columns[0].ColumnName].ToString(); + var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); + if (!string.IsNullOrWhiteSpace(potentialCHI)) + { + handleFoundCHI(potentialCHI, table,columnName,value); + } + } + } + } +} diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs new file mode 100644 index 0000000000..7258a42be4 --- /dev/null +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -0,0 +1,100 @@ +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using Rdmp.Core.Icons.IconProvision; +using Rdmp.Core.MapsDirectlyToDatabaseTable.Attributes; +using Rdmp.Core.Repositories; +using Rdmp.Core.ReusableLibraryCode.Icons.IconProvision; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Rdmp.Core.MapsDirectlyToDatabaseTable; +using System.Diagnostics.CodeAnalysis; +using Rdmp.Core.Curation.Data; + + + +namespace HICPlugin.Curation.Data; + +public class RedactedCHI : DatabaseEntity, IRedactedCHI +{ + private string _potentialCHI; + private string _chiContext; + private string _chiLocation; + //string _name; + //string _digitalObjectIdentifier; + //string _source; + //private string _folder = FolderHelper.Root; + + ///// + //[DoNotImportDescriptions] + //[UsefulProperty] + //public string Folder + //{ + // get => _folder; + // set => SetField(ref _folder, FolderHelper.Adjust(value)); + //} + + [NotNull] + public string PotentialCHI + { + get => _potentialCHI; + set => SetField(ref _potentialCHI, value); + } + + [NotNull] + public string CHIContext + { + get => _chiContext; + set => SetField(ref _chiContext, value); + } + + [NotNull] + public string CHILocation + { + get => _chiLocation; + set => SetField(ref _chiLocation, value); + } + + //[Unique] + //public string DigitalObjectIdentifier + //{ + // get => _digitalObjectIdentifier; + // set => SetField(ref _digitalObjectIdentifier, value); + //} + + //public string Source + //{ + // get => _source; + // set => SetField(ref _source, value); + //} + + public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI, string chiContext,string chiLocation) + { + catalogueRepository.InsertAndHydrate(this, new Dictionary + { + {"potentialCHI", potentialCHI },{"CHIContext",chiContext},{"CHILocation", chiLocation} + }); + } + + public RedactedCHI() { } + internal RedactedCHI(ICatalogueRepository repository, DbDataReader r) + : base(repository, r) + { + PotentialCHI = r["PotentialChi"].ToString(); + CHIContext = r["CHIContext"].ToString(); + // Name = r["Name"].ToString(); + // Folder = r["Folder"].ToString(); + // if (r["DigitalObjectIdentifier"] != DBNull.Value) + // DigitalObjectIdentifier = r["DigitalObjectIdentifier"].ToString(); + // if (r["Source"] != DBNull.Value) + // Source = r["Source"].ToString(); + } +} \ No newline at end of file diff --git a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql new file mode 100644 index 0000000000..8cbec26e41 --- /dev/null +++ b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql @@ -0,0 +1,17 @@ +--Version:8.1.1 +--Description:Adds support for redacting CHI values on data load + +if not exists (select 1 from sys.tables where name = 'RedactedCHI') +begin + +CREATE TABLE [dbo].RedactedCHI( + [ID] [int] IDENTITY(1,1) NOT NULL, + [PotentialCHI] [varchar](500) NOT NULL, + [CHIContext][nvarchar](50) NOT NULL, + CHILocation[nvarchar](500) NOT NULL, + CONSTRAINT [PK_RedactedCHI] PRIMARY KEY CLUSTERED +( + [ID] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] +end diff --git a/Rdmp.Core/Rdmp.Core.csproj b/Rdmp.Core/Rdmp.Core.csproj index 27c2866c9d..da490b6375 100644 --- a/Rdmp.Core/Rdmp.Core.csproj +++ b/Rdmp.Core/Rdmp.Core.csproj @@ -118,8 +118,9 @@ - - + + + @@ -239,8 +240,9 @@ - - + + + From 405f0761a632029227d24da5f78eb7ca14ae1440 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 27 Nov 2023 16:27:39 +0000 Subject: [PATCH 03/64] add start of mutilator --- Rdmp.Core/Curation/Data/IRedactedCHI.cs | 23 +++++++ .../Runtime/ExecuteCHIRedactionStage.cs | 60 +++++++++++++++++ .../Mutilators/CHIRedactionMutilator.cs | 65 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 Rdmp.Core/Curation/Data/IRedactedCHI.cs create mode 100644 Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs create mode 100644 Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs diff --git a/Rdmp.Core/Curation/Data/IRedactedCHI.cs b/Rdmp.Core/Curation/Data/IRedactedCHI.cs new file mode 100644 index 0000000000..814f407fde --- /dev/null +++ b/Rdmp.Core/Curation/Data/IRedactedCHI.cs @@ -0,0 +1,23 @@ +using FAnsi.Naming; +using Rdmp.Core.Curation.Data.Cohort; +using Rdmp.Core.MapsDirectlyToDatabaseTable; +using Rdmp.Core.Repositories; +using Rdmp.Core.ReusableLibraryCode.Checks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HICPlugin.Curation.Data; + +public interface IRedactedCHI :IMapsDirectlyToDatabaseTable +{ + + ICatalogueRepository CatalogueRepository { get; } + + string PotentialCHI { get; } + string CHIContext{ get; } + + string CHILocation { get; } +} diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs new file mode 100644 index 0000000000..dd20bc146f --- /dev/null +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -0,0 +1,60 @@ +using FAnsi.Discovery; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.DataLoad; +using Rdmp.Core.DataLoad.Engine.Job; +using Rdmp.Core.ReusableLibraryCode.Progress; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.Core.DataLoad.Engine.LoadExecution.Components.Runtime; + +internal class ExecuteCHIRedactionStage +{ + private readonly IDataLoadJob _job; + private readonly DiscoveredDatabase _db; + private readonly LoadStage _loadStage; + + public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadStage loadStage) + { + _job = job; + _db = db; + _loadStage = loadStage; + } + + public ExitCodeType Execute(bool redact, Dictionary> _allowLists=null) + { + if (_loadStage != LoadStage.AdjustRaw && _loadStage != LoadStage.AdjustStaging) + throw new NotSupportedException("This mutilator can only run in AdjustRaw or AdjustStaging"); + + + foreach(var tableInfo in _job.RegularTablesToLoad) + { + RedactCHIs(tableInfo); + } + return ExitCodeType.Success; + } + + private void RedactCHIs(ITableInfo tableInfo) + { + var tbl = _db.ExpectTable(tableInfo.GetRuntimeName(_loadStage, _job.Configuration.DatabaseNamer)); + if (!tbl.Exists()) + { + _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Error, + $"Expected table {tbl} did not exist in RAW")); + return; + } + _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, + $"About to run {GetType()} mutilation on table {tbl}")); + var dt = tbl.GetDataTable(); + //todo I have no if below does what I think ti does + //attempting to copy from tbl to a dt then back to itself so that we can medify it + var tempTbl = tbl.Database.ExpectTable(tbl.GetFullyQualifiedName()); + using var insert = tempTbl.BeginBulkInsert(); + insert.Upload(dt); + tbl.BeginBulkInsert(); + tbl = tempTbl; + } +} diff --git a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs new file mode 100644 index 0000000000..64679b8406 --- /dev/null +++ b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs @@ -0,0 +1,65 @@ +using FAnsi.Discovery; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.DataLoad; +using Rdmp.Core.DataLoad.Engine.Job; +using Rdmp.Core.DataLoad.Engine.LoadExecution.Components.Runtime; +using Rdmp.Core.DataLoad.Engine.Mutilators; +using Rdmp.Core.ReusableLibraryCode.Checks; +using Rdmp.Core.ReusableLibraryCode.Progress; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.Serialization; + +namespace Rdmp.Core.DataLoad.Modules.Mutilators; + +internal class CHIRedactionMutilator : IPluginMutilateDataTables +{ + + private DiscoveredDatabase _db; + private LoadStage _loadStage; + private readonly Dictionary> _allowLists = null; + + + + [DemandsInitialization("Location of Allow list file that lists columns to ignore", DemandType = DemandType.Unspecified, + Mandatory = false)] + public string AllowListFileLocation { get; set; } + + + + [DemandsInitialization("Automatically redact found CHIS. Otherwise, a warning is raised", DemandType = DemandType.Unspecified)] + public bool Redact { get; set; } = true; + public void Check(ICheckNotifier notifier) + { + if (AllowListFileLocation != null) + { + var allowListFileContent = File.ReadAllText(AllowListFileLocation); + var deserializer = new DeserializerBuilder().Build(); + var yamlObject = deserializer.Deserialize>>(allowListFileContent); + foreach (var (cat, columns) in yamlObject) + { + _allowLists.Add(cat, columns); + } + } + } + + public void Initialize(DiscoveredDatabase dbInfo, LoadStage loadStage) + { + _db = dbInfo; + _loadStage = loadStage; + } + + public void LoadCompletedSoDispose(ExitCodeType exitCode, IDataLoadEventListener postLoadEventsListener) + { + } + + public ExitCodeType Mutilate(IDataLoadJob job) + { + var redactor = new ExecuteCHIRedactionStage(job, _db, _loadStage); + return redactor.Execute(Redact, _allowLists); + } +} From 35735aee048d67e7c388e944a2161a300a636796 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 28 Nov 2023 09:47:18 +0000 Subject: [PATCH 04/64] basic working dl --- .../Runtime/ExecuteCHIRedactionStage.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index dd20bc146f..698e3ed57e 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -1,10 +1,15 @@ using FAnsi.Discovery; +using HICPlugin.Curation.Data; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.DataLoad; +using Rdmp.Core.DataFlowPipeline; using Rdmp.Core.DataLoad.Engine.Job; using Rdmp.Core.ReusableLibraryCode.Progress; using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -24,13 +29,13 @@ public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadSta _loadStage = loadStage; } - public ExitCodeType Execute(bool redact, Dictionary> _allowLists=null) + public ExitCodeType Execute(bool redact, Dictionary> _allowLists = null) { if (_loadStage != LoadStage.AdjustRaw && _loadStage != LoadStage.AdjustStaging) throw new NotSupportedException("This mutilator can only run in AdjustRaw or AdjustStaging"); - foreach(var tableInfo in _job.RegularTablesToLoad) + foreach (var tableInfo in _job.RegularTablesToLoad) { RedactCHIs(tableInfo); } @@ -49,12 +54,26 @@ private void RedactCHIs(ITableInfo tableInfo) _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, $"About to run {GetType()} mutilation on table {tbl}")); var dt = tbl.GetDataTable(); - //todo I have no if below does what I think ti does - //attempting to copy from tbl to a dt then back to itself so that we can medify it - var tempTbl = tbl.Database.ExpectTable(tbl.GetFullyQualifiedName()); - using var insert = tempTbl.BeginBulkInsert(); + foreach (DataColumn col in dt.Columns) + { + if (col.ColumnName == "chi") continue;//todo + foreach (DataRow row in dt.Rows) + { + var foundChi = CHIColumnFinder.GetPotentialCHI(row[col].ToString()); + if (!string.IsNullOrWhiteSpace(foundChi)) + { + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, row[col].ToString(), 20), $"{tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(),"")}.[{col.ColumnName}]"); //todo make sure this matches + rc.SaveToDatabase(); + row[col] = row[col].ToString().Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + } + } + } + + var conn = _db.Server.GetConnection(); + conn.Open(); + tbl.GetCommand($"DELETE FROM {tbl.GetRuntimeName()}", conn).ExecuteNonQuery(); + conn.Close(); + var insert = tbl.BeginBulkInsert(); insert.Upload(dt); - tbl.BeginBulkInsert(); - tbl = tempTbl; } } From 0b4b4cbe631ab5a53ee094a799fba64cb9e6f596 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 28 Nov 2023 11:21:27 +0000 Subject: [PATCH 05/64] basic ui --- Rdmp.Core/Curation/Data/RedactedCHI.cs | 1 + .../Runtime/ExecuteCHIRedactionStage.cs | 22 +- ...ecuteCommandViewRedactedCHIsInCatalogue.cs | 34 + Rdmp.UI/Menus/CatalogueMenu.cs | 2 + Rdmp.UI/SimpleDialogs/SelectDialog`1.resx | 982 ------------------ ...wRedactedCHIsInCatalogueDialog.Designer.cs | 116 +++ .../ViewRedactedCHIsInCatalogueDialog.cs | 126 +++ .../ViewRedactedCHIsInCatalogueDialog.resx | 120 +++ 8 files changed, 415 insertions(+), 988 deletions(-) create mode 100644 Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs delete mode 100644 Rdmp.UI/SimpleDialogs/SelectDialog`1.resx create mode 100644 Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs create mode 100644 Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs create mode 100644 Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.resx diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 7258a42be4..6ce552d448 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -90,6 +90,7 @@ internal RedactedCHI(ICatalogueRepository repository, DbDataReader r) { PotentialCHI = r["PotentialChi"].ToString(); CHIContext = r["CHIContext"].ToString(); + CHILocation = r["CHILocation"].ToString(); // Name = r["Name"].ToString(); // Folder = r["Folder"].ToString(); // if (r["DigitalObjectIdentifier"] != DBNull.Value) diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 698e3ed57e..68cb20266d 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -21,6 +21,8 @@ internal class ExecuteCHIRedactionStage private readonly IDataLoadJob _job; private readonly DiscoveredDatabase _db; private readonly LoadStage _loadStage; + private Dictionary> _allowList; + private bool _redact; public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadStage loadStage) { @@ -29,12 +31,13 @@ public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadSta _loadStage = loadStage; } - public ExitCodeType Execute(bool redact, Dictionary> _allowLists = null) + public ExitCodeType Execute(bool redact, Dictionary> allowLists = null) { if (_loadStage != LoadStage.AdjustRaw && _loadStage != LoadStage.AdjustStaging) throw new NotSupportedException("This mutilator can only run in AdjustRaw or AdjustStaging"); - + _allowList = allowLists; + _redact = redact; foreach (var tableInfo in _job.RegularTablesToLoad) { RedactCHIs(tableInfo); @@ -56,15 +59,22 @@ private void RedactCHIs(ITableInfo tableInfo) var dt = tbl.GetDataTable(); foreach (DataColumn col in dt.Columns) { - if (col.ColumnName == "chi") continue;//todo + if (_allowList.ContainsKey(col.ColumnName)) continue; foreach (DataRow row in dt.Rows) { var foundChi = CHIColumnFinder.GetPotentialCHI(row[col].ToString()); if (!string.IsNullOrWhiteSpace(foundChi)) { - var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, row[col].ToString(), 20), $"{tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(),"")}.[{col.ColumnName}]"); //todo make sure this matches - rc.SaveToDatabase(); - row[col] = row[col].ToString().Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + if (_redact) + { + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, row[col].ToString(), 20), $"{tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "")}.[{col.ColumnName}]"); //todo make sure this matches + rc.SaveToDatabase(); + row[col] = row[col].ToString().Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + } + else + { + _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, $"Found the CHI {foundChi} during the dataload")); + } } } } diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs new file mode 100644 index 0000000000..04ff30921a --- /dev/null +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs @@ -0,0 +1,34 @@ +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + +using Rdmp.Core.Curation.Data; +using Rdmp.UI.ItemActivation; +using Rdmp.UI.SimpleDialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.UI.CommandExecution.AtomicCommands; + +public class ExecuteCommandViewRedactedCHIsInCatalogue : BasicUICommandExecution +{ + private ICatalogue _catalogue; + private IActivateItems _activator; + public ExecuteCommandViewRedactedCHIsInCatalogue(IActivateItems activator, ICatalogue catalogue) : base(activator) + { + _catalogue = catalogue; + _activator = activator; + } + + public override void Execute() + { + base.Execute(); + var dialog = new ViewRedactedCHIsInCatalogueDialog(_activator,_catalogue); + dialog.Show(); + } +} diff --git a/Rdmp.UI/Menus/CatalogueMenu.cs b/Rdmp.UI/Menus/CatalogueMenu.cs index e4debe2e5b..b9e3011070 100644 --- a/Rdmp.UI/Menus/CatalogueMenu.cs +++ b/Rdmp.UI/Menus/CatalogueMenu.cs @@ -25,6 +25,8 @@ public CatalogueMenu(RDMPContextMenuStripArgs args, Catalogue catalogue) : base( { var isApiCall = catalogue.IsApiCall(); + Add(new ExecuteCommandViewRedactedCHIsInCatalogue(_activator,catalogue)); + Add(new ExecuteCommandGenerateMetadataReport(_activator, catalogue) { Weight = -99.059f diff --git a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx deleted file mode 100644 index 68cc010e31..0000000000 --- a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx +++ /dev/null @@ -1,982 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - - - - R0lGODlhZABkAPcAAPhw5P7+/v78/vLc8P5K5P4g3P4K2v6a8Pym7vxE4Pw24PTE7vy68vyi8Pb29v5q - 6P484P4W2v6q8vx86PqW6vz6/Pj4+Ppq5PpM4PiO6Pz6+viC5vh45vi68PiK6Pz4/Pzr+fj2+PbE8Paa - 6PrO8vTS8PLy8vTM7vj0+Pjt9vTw8vTs9PLs8v5a5v4w4Pxg5vqM6v5x6P464v4a3PyS7Pqi7vpu5vpm - 5Piy7viA6Pau7Pbn9Pai6PTW7vyF7Pqd7PqY7Pic7PqE6PyE6vx+6vx86viW6vas6vxC4PTo8vxU5PzU - 9v4Y2vTm8v4c3P5Y5P5M5Ppu5Pps5Ppc5Pjc8/bW8PxI4vxC4vTi8P4q3vxG4Pw+4P5A4PTg8v4e3P4o - 3v4k3v4i3v4e3v4m3P4W3PTc8vjI8PbS8PzH9PbM8Pqz8PzC8vjC8P5C4vi88P5I5Py+8vy88vut8P6q - 8Pyo8P6i8Pym8Pyk8PqQ6v566vqK6vp+6PqO6vpw5viK5vpg5PqI6v5/6/6K7vye7vyK7PyI6vyA6vyC - 6vx16Pxc5PxM4vxK4PxI4Pi47va27Pig6vie6v6Q7fyM6vxv5/6c7vyO6vyO7PyQ7Pxr5/5i5vqm7vqg - 7P5W5Pxi5vyg7vqm7PxY5Pqo7v5U5Pqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5Pqy7vxK4va87P689PbA - 7vz8/P7S9/464P4t3/444P424P404P4y4PxS4v76/v7n+vro9/ya7vxe5PyW7Pxe5vxg5Pp05vxk5Prf - 9vy48vyS7vp25vzb9/qC6P7E9f7g+P76/Pp45vbW8vq68Pp66Pz4+vrA8vr4+fz0/PrX9ffw9/Xx9Pbu - 9PzY9/ji9PbS8vjT8vbQ8vzN9fu28fzC8/jE8PjA8Py99Puw8Pyq8P6o8P649P7A9P78/P7a+P7w/Pry - +Prj9/zh+P7L9v7j+vbf8vrI8vy28vyv8vzy+vrO9P546vqk7vyJ6/jK8P6I7P4w3vrs+Pz2/Pz2+gAA - ACH5BAAFAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAI/gADCBxIsKDBgwgTCtTgwIRDBxUU - SpxIsaLFiwJR6GnzxkO1iBhDihwZkpCMkzL4aCDJsqVLgRXaoJRBYOXLmzgphpgpA4LNnECDDnTA06fQ - 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 - PI9WCGup3FW1DOO35OjwhWKtEKq2FlwSrACCRQvsTPM6IfRILir6nBOzilYclCr1aSHWjeS+GknTKIZo - i+FeYxN6fpJ2LCko3SFX/BY1E5H2AdJ2jOS+dkWhFm0d0qbutAkAua0Fcock8Nv7dOrYEtFFclXboQr8 - lhTMU7euC5KM5K5IA9o+q4LrguaJJFvcK4LFh88a4n/r+CIlhuuJQB/ejcS+G8AVp0g6XPFfCXZV4VKp - tQ7VzWxEO9q7Mrf5RIaLWuPq/CimRS3Bfx4UaCBWsTUn+oykYQG25vwlAQEAIfkEAAUAAAAsAAAAAGQA - ZACH9nzk/v7+/vz+8t7w/kTi/iTe/gTa/pTu/Jzs/Eri/Bzc8szs/Jru+Pj4/mjm/jTg/gba/qTw/I7q - +oTo+vr6+lLi+j7e+qTu+Ijm+vj6+vj4+Gzi+GLi+LTu/Pb79ojm9sLu8vLy9pLo/hTc+fP49vL19PL0 - 9OLy9NTw9Mbu9Lbq9Kzq9KLo/Mj08ujw8uLw9erz9Oby9Njw9M7u/lTm/jLg/HLo/ELg/nTq/jrh/hLa - /Jbs+pLs+lji+Ibo+tn1+Jjq+HDk9rDs/hnc9Lzs/JTs/JLs/JDs/I7s/Izs/mTo+pLq+orq+JLq9p7q - /Fzk+obo+JDo+Ijo9try/iLc/mrm/kbi/FLi+OD09OTy/FDi/Ezi+lTi/ibe/DLc+kjg+kTg/CTc/CDc - /B7c+Mvy/hDa/gza/gra/gja+rfw9Nbw9tLy9s7w+L7w/kLi/LXx+Lzw+Lru+Lbu+rDw/Krw+qju/KXv - +qbu/KDu/qLw/qDw/pnv/J7u/Jzu/n3q/mzo/nbo/IPq/Gnm/Fjk/E7i+mLk+H3m/oTs+nvn/Dze/Cjc - /Hjo+pjs+pTq+pDq/ovs/pLu/HTo+Jrs9rLs9MTs+JTq9qbq+prq/GTm+pzs+Jzq/lvm/GDk/lTk+p7s - +KLq+qDs+KLs+KTq/qrx/C7c+Kzs/rT0/FTi/lLk/lDk/kzk9r7s/Eji/rjz/Ebi/Ebg/Pz8/Dzg/vr+ - /sn2/D7g/DTe/jDe/Frk/tX4/Nr3/izg/ize/ire/ije+mTk/uL6+mjk8uzy/Gzm+r3x+mrk/Gzo+nDm - /G7m/Of4/HTm+snz9N7w+Ov2/vr8+nLk+nLm/MPz+pbs+eT1/iDc+NTz9N7y/Pr7/Pj8+vb69vX29PT0 - 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 - +1Hq2vWuediQ3SxckGaQI7StcF6CgDAEfiaixAFoQTrFm5FWzPZmFZauwcQa2ndkhBnOOAM4gXARfEDi - EaNARg5la9lzGAwWLDYIPnr7W7GsoQJmUAAGjDsSAYxitu4VCP6WhXyQ6oYWH40NwCyH+BpczJYcYFoz - bvXH3sCKbiQ4FYkH1tHf8ao5ywa5xoAD2wop8jIA+JhtaRCiZ87GEh4ZfnQ9TrzVCKjYIJXebCxhwVtG - E3aXPrZsfCiN6IOANrT+CHRV4jFbdnwq1BH46FHbm0ssz3ayoG71QTzQ23bIWig6tSw0RIbrEgM2tH8+ - 4KAtuw5DF6TZ6Fv0WBu9wnbMdrTGETZC4tFbbv6oHrMdRfCwTZHecflH7JjtNyzCboq4ObCeRRAszOu+ - elNk19uGkTUIvVX/0kXcCSE2o2EECwlvtUEHZ/NFMEzWH1ljG3wFN70RrhB4tKMd0WYhoCs5bpxHYwTA - JudLeo+d8onclrO6bTlSNMvZfMv8b3vta5xvbpEmLWXnQQkIACH5BAAFAAAALAAAAABkAGQAh/aM5v7+ - /v78/vLe8P5M5P4e3P4M2v6c8Pyk7vwk3PSw6vy48vyi8Pb09v5s6P4+4P4O2v6s8vx66Pz6/Pr4+vp6 - 5vpY4vpE4Pqu8PqY6vz6+vj4+Phy5Pho4viY6vz4/Pz4+vj2+Pj0+Pj09vaW6PbE8Pam6Pzc9/rr9/b2 - 9vTW8PLq8vTG7vS47Pjk9fTy9PTY8PLk8P5c5v404Pxa5P587P484v4c3PyK7PqG6vpw5viC5vqh7fig - 7PTp8/TQ7vTI7vS+7v4a3PyE6vqc7PqW7P5y6fx86Pic6via6vaq6vp+6Pp66P5O5Pq88Pp85vpm5PxK - 4vpY5Pbd8vw83vjS8v4s4PpO4P4u3v4q3v4o3vww3vwm3P4g3P4U3P4S3P4Q3PzN9P4Y2v4W2v4U2v4Q - 2vbU8PbQ8vq48PzE9PzB8v5D4vy88v5K5Py08Pq28Pqy8Pys8P6q8vyo8P6k8Pym8P6e8PqT6vp25viO - 6PqM6PqA5vpu5vpe5P6H7P6A6vqM6vqC6PyS7PyI7Px+6vyA6v6M7vxr5vxY4vxC4Pw23vws3v6T7v6a - 7v5j5/5a5vyU7PyW7Pqk7Pim7PTO7vqe7PqY7Pii6vyY7Pya7Pxh5vya7vyc7vxf5Pyg7vqq7vxc5P5Y - 5vqk7vqo7v5W5Pio7P5T5PxS4/i47viw7vqs7v6y8vqu7vxO4v689Pa07PbC7vi+7vw63v7A9Pz8/P7c - +P464P4w4P444P424PxG4vxM4PrS9Pp45vxY5P76/v76/Pzx+vTa8P7I9vTe8PyQ7PjA8Prd9vLy8vyN - 7PrF8vzn+fby9P5+6vja9P7s+v586vx05/7n+v7Q9vr6+vzi+Pr2+vfs9vT09PTs9Pq/8fTk8vjW8/zV - 9vbW8PzJ9PzA9Py+8vu18vq08Pyu8Pyq8P6o8v6g8P669P7D9v78/P7g+frX9fzz+/7M9vjG8Prj9vrM - 8/zs+vfx9vjf9P7q+v7U+Pq/8vyv8vjK8vr0+Pbt9P566vzQ9f5K4gAAAAj+AAMIHEiwoMGDCBMKpLah - wYsGG2gpnEixosWLGAVag0QqVJEGEjOKHElSJCQCKAlYmlCypcuXAieESkngEUuYOHNWpECTQBNqOoMK - LRii58+hSBHSokDt5sUGRimIpAU0KcYJCwj9WeC0IlSaTaRenDDHkaNPG6xaJCejrQwMGL+mDHuRViYb - eG3wqKo2oQAHbmU4WvY06kV6a/LaaJK2r19HgWUICklRLkq6FTdIUGxjjVjHCItFltHN4gvDFUtwtiGB - MmiDLiAHhtY14WmwnxU2kMF5zbHXCWkhGM2m4u25uZWWWg3JNfCC1wAHdqCP4vHLyQ/6IMC5SfXnSp3+ - jEbg3OB1n9kLUgO0Gg543RIiO4I38TzmhN4ecHZU+33BKqPh0B9B9qU30AY6rBaPfwpRIFpkYShUoEKx - rEbIgAwO5IImkRlhYAATIsRMKL25kKFCZI3mhm2oGTQBD6sRceJE80jnlgPyIBSiQdvowpkp382YkDGj - cVIeiC0SREEOqzkB2lITHDnRBn+M9pt5SQ5UhX6KHYKhQrQI8OVBE4jDjxGb0JcRN6MN0Z9l6BkUwiGc - PcBLRtRw48AtNhQxJkHhOCCoA0bEEQxG1BQxmjIGwXnfQK+sloOUB9Hiww63WKGpFZJURAsOgw7Kjxp/ - EuSDbG4ZYU1Bjia3jyn+nLWBwlOX2LDppjR8SJAhoYbqBzuUEkQLBqORw2qS1GSwWqeZwXLKrbfS0NhE - s/Ta6wGzVmaEfNkK5ChfAUyRmGI0rDoRNcLwAS20k4Cr0AJGWDuoEW6Yq9A7o0VCWVFgOUXNE6tpM5Gl - esyw7qYzZKArbJzIO2gN34xJgR+jsTMQT2BRls9qOrjLqhJrHLxpNGriyY0fDguazAmUOjPahQIJYApN - j4Q0gSO9OZPQBkAgIrKmpORTqkIhqMFPyg4w0G1BE1QyWoQCMUITJiGFs1oxR05ADB4/W/HAJwtfNE8c - 8TpsxALpRReZsQJ9EElHmNh7AGcELD3QPncYLPL+LTjY/dIELkSC9B9h1EbLApF9M9BSDW3wmSucwUXU - K210fYgzwbZEQRVDIC1INUxT3BYOAlA0gRF5eUjQBj9o0nUTxgztUghOlC2vEXMcOpAA6ciRjuxYRcLV - 4tsskanItvAQwmu0XIMA0vyEI3twSlZiy89YBOK3Y9TQU4zn00/0ges/a8JN5mpRkE8zKSsOEzk/64JG - 2KCJII7toa6CEw4H55KBCEISlgs8YS33vQQf6+pFyQJIEGo4A1SCYkTpYPIBR+AqHuHzzwSmsYBpTBAn - WCmCNjLIwBKa8IQFocUEVsjCFrrwhTBcIfpcMoEUvOCGOMyhDneYwxTwhRb+1nCDH2pAxCIa8YhIRKIf - VkFCi3xAAlkogBSnSMUqWtGKWpBAdSbghiR68YtJ1N9QJHDFMprxihJY4RDByMYv+mGGGZlAFM9IRzNm - gQITWGMb92hEPwyFGnOsoyCpmIWIdJGPiKyBGIMygSMM8pFSrIBUuKjHRHpxiR8MigigCEk6ZqECzHAK - VTZAylKa8pSoLCVT+jIBCqTylbA0JR5RSMta2tJFtUhDN5qYkAnEYQhuoN+MqsEAQxhTDplsiTWiYIBm - UiEI0zphMFZhzGoawh44QUAzt2mATsBAmK+hQBoYYc1qpgMnveDmNsewgxXAUS206EYjymlNbMJEEur+ - 5GYWTNCAEy2jDvS0ZgRyYo0m5JObF2BBNF/TjgUEtJqMCEMyZ5eBAhy0mRDowAA8lpQJfIOcDzUEG4IU - AAHUYhi1mChCBCCNWkhDgDoAw0UNIAQAZOOdJKHFCQ4QUkNEAAWu4eIBhroAi0zDFUidBtOqsIuZGmAL - CkhBUpYRgZ7agR39scdQt7qOxU2gKa6RBlLH+tKCUCAVWHDqFX6w0Je0gw09ZcQ30vMBOmx1qEqNyTRW - sQoPDqQWY0VqOj5wEBHoQQwz/QIHYsBLpoUBpA9dwPYEkoa7DrWrAplGBDYbgbwGoB6BRWotlAIPKEBg - pgUgwQtccoJz9HQO8Pj+kjzsYNlFBmAVnI2AbYcRWlfo7iDUyEcinKqIFrS1IvLAR08b0Q2OLm4Vlj0A - YQaS280S5Bm9nUawKDAJLTjVAiqY3gTGGVJGpGF5E9mpZWfhmupGwDX26O10FSKCPXhhpl7IwdBo8Y2e - ugGoppODZc9B0gC4tysfmEVoh1EqWhyjE07FREUmQIeHMiC2Ro0u1BZ34IIANrSYpQg1YiGLi66hwRUu - ZyP6Ac5gnMOyckgOLTpMEN6FdhaEtQgFiNCFfCbCuQUJgzUZAY4CU2QW0T2BQWZc3f6INbSlwcg18PAF - bsrIIsM4ACNWAWCMSCO64ugPk3M7IHf09rcWmYD+GWgwhgTkAZwtEQB072qH7Y2ZsxwNRm/dgdOFbCAF - cG6Jeu+ahiPdebPOjW9o55uRPpfkA+awLB2MLJBDR8C5H0hHaH9nS3dEtx/BcS+Q19Hb0dLSxZaNwIcs - DWQB8Daws2gHLV0RXdCFurpA/mx2UbiM6A7v1rnNdQCOGtpnmHACEbCsHXIEJlFPpB29HYZKGdSN6A4j - c6ymyIcDa2sh1VXS9gI2nk2n6cByWkhZtmyUB+ZsilSj1EKarWXxsd92T8TVvZX1idAR3QU2G9cWwW5o - 7cmgCdD2rmyAo6Wnp+ixzuJEFL7rOdDM7iZfJMHmPhEtpnFXz3qKxhYh9Vg+Q1zwbvCV4BZZeEaq4Q53 - dPuW4t5sY2FOEZDTPCjudfTNK4Jbztp25znRLGc9DnQQ7rWvMy86majB9KTDJCAAIfkEAAUAAAAsAAAA - AGQAZACH9obm/v7+/vz+8uDw/kbk/hbc/gba/pbw/Jbu/Bra8srs/KDw+Pj4/mbo/ibe/gja/rb0/Kru - /HLo+vr6+mTk+jze+q7w+pLq+vj6+Hzk+Gzk+GLi+Fjg+MDw9pro/Pf79szw8vLy9pbo/M/1+fb49/P2 - 9vL09O709OTy9Nrw9NLw9Kro/hTa+uj39PL08ury8tbu+Nz09vD29PD09Ory9ODw8uzy/lbm/hjc/Kju - /Kbu/KTu/KLu/KDu/J7u/Jzu/Jru/E7i/nbq/i3e/hDa/Ijs+oDo+lbi+prs+Iro+G7k+Jrq9uXz9MTu - 9Kjq8tzu+pDs+KDs9Lrs/m3o/IDq/Hbo+prq+JTq+nTo+Ijo+JLo+nDm+Ibm9Oby/lTk/kjk+mzk+s/y - 9tjy/Dze+lLi+kbg/DDe/Cje/CLc9Nzw/g7a/gza/gra+NHy/jbg9tLy9tLw/Lzy+rzw+Mjw/j3h+MLw - /kzi/LXw+rrw+rbw/qby+rLw/LLx/K/w/Kvw/qbw/Kbw/p7w/n7q/nLq+ojp+mDk+HLk+nzm+nbm+lTi - +krg+pzs/ILq/Hjo/obs/Hrq/H7o/IDo+nro/Ibq/o/t/Ijq/pzu/Gbl/ETh/Dbe/Cre+LLu9sLu9Mju - 9LLq+Kzs+Kbs9Lzs+Jbq9qDo/l7m/lDk/Jzs+p7s+qDs/Fvk+lzk+qTu+qbs/Fbk+qbu+qju/FLj+q7u - /k7k/kzk/krk+Lju/rrz/Cze+Lru/vz8/EHf/vr+/vr8/sX1/D7g9sTu/t/5/DPe9NDu+mTi+JDo/Nz3 - /Eri/Jbs/iTe/Ezg/iLc+K3u/h7c/I/s+J7q+Hbk/Of4/hze/hzc/vj8/hre/hrc/Izs/vb8+pTs/tf4 - +t/2+Or2+nzo+Kjs+n7o/kri/pru/PD6/jTg/Mf0+sby/kji/kbi/q/y/pju/tL29q7s/Pv8/Pj8/NX2 - +vb69/P39fP1+vH4+OH09Oz09ur0+tf09uLy+Nbz/MDy+r7w+Mry+MTw/Lny+rry+rjwAAAACP4AAwgc - SLCgwYMIEwqcwACeC3gM1imcSLGixYsYBboDQorUoncSM4ocSVIkkBsob/QIWbKly5frSKW80YDly5s4 - LWKYiXJCzp9AEZLgecNn0KMKJ0ywWbEEUaMY1zFFWnFCHEeO4kxV6JQn1Irr+kyZYoEB1Yt8GqhtcAdj - 15lfJ67rQaAuAR1xzx4UMGhtgyktLr5NmTehvFl2Cdwwq1ehgCl+GyDYenBwT4sMqCQmIItx44ToIjeo - ZxHeU4tzNhNgRPmzQG2iGRU2aNprRXiXNs/S5lrhOlei8d0+LXeZaiCteweI19fvoHgUa8OlSM/L5hvQ - lSvMJzpCdOIJJ/5cUJ1H+0QSjCIDniid8EQxXzZPmW1+4D3RlJK3v5yQQRXVYdQ30QQIiDaCQvsVpVAH - qk2SnIABMAGZX4JgkFCC9JVAymZ2BAahbxaIxs+F4BW0DiuqofIhRSUIEdkg4SCEIUJMlLIZKSSsSNE+ - ou0gY4kDMVCJanK4to5SDx7EQBGi8UYbkAK9EV9iEtCXkFRJErQOP1Q8soCHGN0XmTVTWaZgQSRIsNkX - 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 - 9t7y9O3y8ury8uDw8tzw8szu/hDa/lDk/ize/JLs/Ejg/mfn/jDg/g7a/HDo+nro+lrk+orq+ITo+HDk - +JTq9qzq9L7s/hXb/I7s/Izs/iLc/Izq/Ibq/Hzq+obq+oro9p7q/H7o/Hzo/Gjm+I7o9OXy/Gbm/GTm - +mzm+dHz+Hzm/k7k/kLi+lzi9Njw/Djg/DLe+k7g/iLe/Cre/Crc+Mnw9Mru/L3y+rrx/jng98Du/j7g - /Kfw+LTu+qru+LLu+LDu+qju+qbu/KDu+qTu/pXu/J7u/Jzu/nDq+o7q/Grm/Ijq/ITq/ILo/Gzo/G7m - /G7o/nbp/oDs+oPo+mbk+Ibo+nTm+l7i/IDq/IDo+nzo/ojs/pDs+KLs+Jbo9rbs+J7q+Jjq+JTo+nLm - +pDs+pbq+prs+mTk+p7s/FPj/EDg/Dze/DDe/lbl/kzk+qDs+qLs/rTy+Kjs+qTs+Krs/FHj+LLs/E7i - /kri/Pz8/kji/ub6/kbi/kTk/kTi/D7g/Drg/DTg/r70/ire/PH5/ije/ibe/sD0/iTe/sL0/sT2+mjk - /ETg/sb0/sb2/sj2/Ebg/sz2/Nr49PT0+N70/tT29ur0/Hbo/iDc+Jrq+sHy/LLx/vr8/Mn0/rDz/Pr7 - +vb5/Nb2+fL59ub08/Dz9Ojy+df09Nzw+Mvy9NLu/ML0+r3y+MLw/Kzw+Lbu+q3u/KLu/qDw/rn0/vz8 - /PX8/N749vL2+eL2/tb49u31+sfy/Ljy/Mz1+Lru+qzw/KTu/nDo/HLo/hzc9tHw/N7399Ty/OX4/t74 - 9OLy+MTw/K7w+rLw/LDw+rTw+rbw/q7y+PD3+Nry/jzg/pzu/Fvk/FbkAAAACP4AAwgcSLCgwYMIEwpE - lqEBrwYZRCmcSLGixYsYBSZjQ4PGhRASM4ocSVKkNgcoHaBDVrKly5cCRdFI6eAOS5g4c1akQBPlTZ1A - gw4M0dPBT6FIDVJAFvJig6IURIo6mrSiKFhBDgxrWvFpz6gXRRFTp4dehqoWycVZG4ccRq80wVptA6Mu - jDNy0SIUYIRtHD0onEK9CM6SXRgO8uo1KMDvWjZcFcJNqTghhSKHYViqvJjgG8dxdlmcjJLzwWiZYdCJ - 3JngO9CEqCIknbjrncyWwLVOKMoa6GldB08UhSY15N0J8/X1a2QZRdqmCf5ykNlBK+QKh4Ge91y4ZUep - w/5gV5iMEOh3E6FP1JdanezxBJ+BRsCaoPqEGYykBgFfIYXPjvGX0H0IyZOaEPX1NxAKejhGSHQEGvQN - DbgFpiBvxIAGxoDeFSTKGamtceFE+ezhmB7XHRQhQcwYdhgNyYw40TiglTFbhwNRIENqw3QmylIjZXAA - aLoZtKJA7qSWwHvDiZIgQqKAQcgeZViIUS+gucHakRkAk9ozGSGjTwJVUHHBkwUZs8eaa5IT40XIZANa - OUbiGIA5qUmApoe/yFEFAYASYI1F/LDJJiHGMHnQLw36tccHBa2YD3WHWWLlcx1QEWigNERHUKGGsulG - OxaJcg9obtnXITKGpIaNRf4ZeOHAppvO4elAaoZqaBkpThSCiX4BpmpPR7WYGYwUIZMCDrTSio2iBcFS - h65s1gHGmwpJAVo2TRFFbI46pDbOcOI4UkqzgZaSCbQGoVAGtWweAAKaFEgAGqkC8dRTU1KkpgS0DZgB - A7qBBlJkmL24Ae+a/Bx8EDigBRGSADOldMdAyKSDWy+WscMEwYBCIQW7E2VgzJQLz9NrQcicAZqAAZyU - EjoD2ZLaI/UhEw8fIBMAg1k4LUPOwnsQMg2kBinnWKoBfMARDdlgG0F1Kw9EQiKjgGyKBPkAJQoK2RB9 - gDKswRLgQD82lIFcsmRmj0EZmGNJzwn0smdJFDwjA/7RbKBHEDJusOWGABTxZVcgR1HADh49OzAOyS8l - Mw3K8BKzsgC6aKIL5AEUAwYbsPwkCjM6/EnwJWdgu1g+xBCNKOe8/a3GJSBXQcSlrSEDzht8wz7RB3n0 - fMMzd1dFgRQHLAyzS5qAbMkwvlcVgpTUbgiTG+iOcoHqMgaAgjW6Lt8SOc3WgXv3C4HDAJsREA7TBzcE - ysTI6PO2CyzhAIXM51vV7///AByJkwZIwAIa8IAD1AsyHMKLBjrwgRCMoAMbkJdkgMENB8igBjfIwQ52 - 0A2rKAZSPlCEfdTghChMoQpXuEIxFCFGUfKgDGfowVUgpQgszKEOWVgEpmCQhv5AnKEbhCIKE+7wiDrc - BwVE8cMgOnGDQwwKBYyIxCqmUIkBWMUTt5hBWBARh1YMYw0wEJVirKKJXJShG2DhvqAko4RiPOI+MPAN - 0SGDAnjMox73yEc9Rm8kd+yjIAe5xz8G8JCIHI8AvKELb7QRJsXQRBDIcavuveMY2sjkNh7ZkmRYogCg - vIQ8zhLAVsAik6jUBjxwcgZQurIAFYBGJZHzgXJEIJWo1AVO6vBKV7rCAyoonl5EsQt84DKVq4RJK3vp - SjH4oAEXIoUmjpnKbeQkGf1g5isl8QVSIqcbsqAmKiOgDE66JBmRqIE2XfkHFhiSJMgAwS3FqQ1jqG6R - IP5wpEWSgQ4jlAFpAfjaErawzgLEoAfNEGZJROENY9JzG63gigBkcYyKzqIiH7jECzYKA4AG1B3ZLCgB - EAFNpLRiG/TUBj7aIZtdVPSlpIhJA0hgAhJQcCBn2KhOFREZClyDDAUtgBWkMUuMdMMWKY0ACDjzAU28 - tKKiCWg++gABCFTAOQHlg043WgAnHCQEPWBFQWMxBWYoVCGiUMY8xSkLrB4EBE+taEwDgAwmGOCuBpjD - QNCw1Y3ywJsEEQU4/hDUGiSBBC65ZEo1gYIntSKux/DiQiCAVwO84CYUQEVfgeCChCBDCpIIai5+ANiL - tGIVKV0puygaVytRoLJ39f5mNfr6Ai2UFCEUwIYYgvoEfShUALZMaTm4d5BLxlWXOYKtAW5LgQnQdgF7 - yocOXFHQVwTBnAgBQUphUTWEIGMbcdXEyjKgXMQOpBdA6OsDVEARwVYgqGywyjSpqYl3CNOlcY3qQBpQ - 3sBigLYemCUy5DGDdcLgbqKYbyrJCbtuOPWp26AKf2Fr3oHkIwZ9XUcLLkKBNjyAmZKAnDJwac+M6AKy - frNPfwuyCdpuoLQTEYcdYvHKTFxEnhFYRXcpQgrIzmJLKyZIBmZA2xJkRBTPeIIrVNGDor4EFpDd8YQr - a4LT0NYCvBAJQ266GHhAVnz7VW6VDYKMCtBWBGdFW/5nPgDep2qCuAKZMl7HbBBwrKOvXFgBIn0BWf0a - ScxQagJtAeDkb0J2FYqS813pbJBlPGCzVwggLlAsGUAnxAy0BcRt0dfjuN4CTYo2AKOV4gnansB/AlhF - lNNj6YRIIb1bpUSFZeSNLz+n1d79A21H8M6gsDmu2/DobHCNEBQMoa8KcEb34BpXb3SF2Ah5BG01UGiT - Hrp4oR41QpKRis26Y0S2gOz5hg1bbSPEHLTtR5qBIgDI4sIpQZ4IBfqR4V7DRBRtrqgmugFvCl9EH7De - qCvsDRP8QvUt8R7Of3UqsQsxFBaycDbC/X2RDByiFrV4BIwTGamEU4QCDVm3/zbIC9ssc1woJK/spk+e - EwpQFq82qDbLd0KLykZB5DP3jwo2YAMbVKBrOdcJBXhhggHUdOO7CQgAIfkEAAUAAAAsAAAAAGQAZACH - +Gzk/v7+/vz+8PDw/krk/hrc/gra/prw/Kbu/Izs/Cze7u7u/MD0/KTw9vb2/mro/ire/hba/qry/KTu - /HLo/Pr8+Pj4+nDm+lTi+kzg+pDq/Pr6+Hbk+G7k+Lbw+JTq/Pj8/Pj6+Pb4+PT49sbw9p7q/N34+sfy - 9NTw9L7s9LDq9vD19PT09Nrw9PL09PD0/lrm/hzc/KDu/J7u/Jru/Jbu/JTu/Fjk/m/o/jDf/hbc/Irq - +oLo+mjm+Iro+pbs+H7m+Jzs9qjq9Mju/Jjs/JLs/I7s+pzq9+Hz/IDq/Hrp+pLq+Jrq9qLq/Hbo/HTo - +njm/hja+nLm/lrk/kzk/ELi+lri+lji/D7g/Djg/DLe/NP299by9tby/jri/Mz09tDy+Mby9s7w9srw - /Mb0+Mbw/ML0+7fx/j/g+Lrw/kbi+6/w/Krw/Kjw/qjw/qHw/Kbw/nrq+orq+m7k+JDp+Ibm+nro+mTk - +qbu/ITq/H7o+oXp/oDr/oru+nrm/Ibq/pHt/Gzm/Ezi/ETg/Drg/DTg/p7u+mzm+pvs9rru9rDq9M7u - +Kzs/mHm/lbk/GPl+LDu/F3k+qjs/lTk+qju+q7u/rHy/lLk/lDk/rr0/k7k+rLu+LTu/Efh+Lbu+L7u - /ELg9r7u/r/0/Pz89sDu/uL5+qXt+vD4/Izq/ibe/E7i+tz2/iLe/iLc/sb2/FDi/iDc/FLk/h7e/h7c - /FTk/O759N7w/vr8/sr2+qDs9N7y9OLy+tDy/sz2+KLs9Oby/s72/Ob5/jrg/jjg/kTi/pzu/tD2+vj6 - /OL4+sry9/D3+OH2/Nj3+Nj0/M/2+Mry/Mj0/MT0/Lzy+MDw+rLw+qzw+qrw/qbw/rj0/sP1/vz8/ur6 - +vL6+uT2/sj2/PT6+tf09Oj0/Or6/tD4/LLw/LTw+rbw/LDy/njq/jbg+J7q9ur0+rzx/ors/pru+uH2 - +On2+r7y+ub39Ory/tj4+sz0+M7y/LTy+sLy9O7y+8Py8vLy/MTy9O30/nTqAAAACP4AAwgcSLCgwYMI - EwqsYMGBQwujFEqcSLGixYsCQ8Dx5w+BiIgYQ4ocGfLag5MP2FQgybKly4U4UD7wt/KlzZsUj8k8WROn - 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 - B9nADXSbggPCEbPjxbBtUX0QcMiisIP4Mo3IfFUAbmXTCKmAHXRbAnvepAJWpXOKWQRshMADAoVVwDrI - B+i8UkwhpjYArRGSC90CwdguuWxeGQ2cZiNEBFUorCy4YCJgYPZK0TE3Qj6h20e82ScCwKwxmPJiZz1i - 0uAmySiSbVP0xtvIFVkFLMaag4CT5L82DfDBO6trdR1YqEu4t09GUQpc4GKgoOl3TkeYIAhBqGPKlWQ2 - wi0iSAdo3IDqpXjKjRJzwIJ65jexgA46GwFE4zwkFphDZynw8p+Hpx8A6OoFRmB0n2yABQOIOgtorJqA - AAAh+QQABQAAACwAAAAAZABkAIf2guT+/v7+/P7u7u7+ROL+JN7+BNr+lO78kuz8RuL8GNryyOz8lu74 - +Pj+aOb+NOD+Btr+pPD8iOr6dub6+vr6TOD6Qt76ou74lur6+Pr4fOb4ZuL4sO74lOj89vr2jOby8vL2 - luj+FNz47/f27vX08PT07vT01vD00PD0sOr0puj08vTy0O78xvTw8PD06PLy5PDy2u7+VOb+MuD8cuj8 - QOD+dOr+OuD+Etr8kOz6kOr6XuT4gub4qOz4cOT4lOr0tOz+Gtz8juz4puz4nur+ZOj8jur8jOr6gOj5 - 1vT20vL2our+Itz8XOT6eOb+SuL8VOL8TuL8SuL8SOL6VOL6UuL6TuL+Jt78Nt703PDy4PD8Mt78Ktz8 - JNz+Dtr+DNr+Ctr+CNr02PD6uvD2xu/+QuL8rvD2v+74tO76svD6rO78qvD6qu78p+/6pu78nO7+ovD+ - oPD+mO/8mu78mO7+e+r+a+j+cur6auT4i+j6VuL8ger6luz6huj8ZOX6gOb8VuT8UOL+hOz8eOj+iuz6 - iOr+ku78dOj6dOb8ON78Lt76jOr6jur2rOz0uur4ouz6ZuT6mOr+Xeb+WOT6mOz6muz6nOz8YOX6nuz8 - Xub6oOz6ouz+qfH+VOT+tPT2tOz+TuT8RuD+TOT+tvP8/Pz++vz0wOz+yPb8Ot78POD6XOL+MN78WOT+ - 0fb+Lt78WuT82vf+LOD+LN7+Kt7+KN7+2vj6aub8aOb6buT6cOb6k+r+5Pr8aub6cuT8bub32/P84vj4 - 5PX6xvL8t/H+5vr80vb+INz24/L++Pz8+vv8+Pz58vn29fb09PT8yvT07PTy6vD53fX31vP04PD6v/L4 - zPD8svD4wvD4uO76tPD6rPD8rPD8qPD8nu7+nvD+sPL+wPT+/Pz+zPb+1fj83/j+3/r42vT86Pr55/b6 - y/P8v/L81/b25/T4uu78oO78ovD+uvP85vj+xPX87vny7vL03vL20PD8sfL6tvD6sPD8pPD4yfL6uPD8 - 9vz88vwAAAAI/gADCBxIsKDBgwgTCkzWoAGzhqQUSpxIsaLFiwIzbKtTx0yDiBhDihwZkpONkzbMgCTJ - sqVLAXVQ2qiz0qXNmxUzyDyZDKfPnwgb7LTRE6hRhcmS1aQodGdRjE+PWiTV4o03Z0slNpUZdSIpeHsE - jaEg1SI4QWgFqbu4FWVXiWaKyC2S5m3Zg0LSCiI0z2JbnhaDOZhb5A7ZuxL1ot3md6jdgxQQEC5i5zDi - hJwUCyJXkZnjikomF2FwWSI6zXIeF/TslCKzOpMdoCstcZhmZ64/Txwjeg1ticvy6hXiYSJrrhNJ3Jl8 - p/hvhek0wzOuO2GyC6LXPlfYYI7mcxKP/ruVGE30HtXbBUbT3CYrQfGAEzY4InpdeonJ1miGpRA+Ueii - zeHefQSdptgBqvmXIGyEOQAegQqRQo1mLSSkIEKkZCNaPhBOtIwhihHiD0IXHsSOHZPZkEGHE0Wn2D0k - VkcQBW+IVuFlpCQlUgNxaBbPQSUWZI5oEgwYoQACXCRAC97IMUpfGAGjGRxZ/fVfQQ1IIBpnFyUTjQQy - FLFGkhS1csCZB8gBjnMVJXOPZsUYZOVbwojWzUWkkFCJDHzyOUxFnKCJpjfFGGnQCIQoZogyWMoYwAg2 - TGYHlK6hUUSffdZhmUKBCoomJ8BYNKFi2g00J0HJqCHan0xVQwOm/phqWqannj7J1AEhUhrAqQMFgyJh - dayI3zFgwoopPugRJI4ctKIpRwtsJpSEZmYQZOVKyfAhWjoSkTLCBZMY2+ckaiRb0DzDNItmHOsYSkEE - moW60FArJSGaEOgxw5u4fQoxW0ikANOGumeu8SBCwWj2BkgwyVTHQMnsIZq8kCkxCL982lAoSxQU4w3B - B8Az4kGk2KYYfwJ9I1O1AjkjWhsYspMDxmGOddMy6jCrrhzOMGrQMonqdWMAypjBkRnCBvANc8scREIP - ktDszgg/kYLOPSDDAUtWLSg2zkCkUNBQA0+dMhk1cl5jB80SBGMoS8nAEgHIZhwsECnwohUB/pkKCSDE - XDlERcEvddBsRzpv25SBMzqrq060AowDzjiJL9TCNi08lScfNEuSRtKX4QyyN61UblEpaUSNMSW60ma1 - GSB/Y/pEpRSO8R7RzG4UBUnAQXAtN6mDsQPCmFtaAy00Lqg8N8UhriRqRMuiQCPAQyvwNoFjLAJ2T09Q - wJ0e8A3fLSmzR5+DtOt93+SIU85PybSwBlbr12///S6Rov/+/Pfv//+XYQgzBkjAAhrwgAUkG6pOwYkI - OPCBEIygBCXIiVOQzyfIQAAoCMDBDnrwgyAEISgQ4JxTTPCEKJzgKY6CgBC68IUhRIBSGpjCGqKQE0Yh - xQZhyMMXgoIs/jS0oRAhiEOgJGOHPUyiB0HRgACYcIhQjIA4cthCJVqRAAggiwDEEcQonpAT4rggTjKg - wSvycIQjiEoyKMDGNrrxjXBso1IQs8ax2fGOeMzj2ChgPPz58Y/1I0YtdvGTUsCDDuroY4fmIQ9PONKC - N1GGAx5AySbso4l+9IA4HMlJTxDSJtagpCgf4IR6bGp6pajFKDrJya/ZRAijFOUNMGCC9e0CHqzs5Cdd - koZYjvIJaGDGIhuZS06u8CYZ4IUvR+kHe2DyOf1oRTE5CQ8uQawlGWhDGZYpSiQcQ5H5U+U0HVmMaC3j - DbnohvQSkgEzSMAMpSDICCTATUrWgAgk/rgMMcAxTk+conUNqIEBBkqAeEokA00ogEIzATpSJOEW9XxA - IM7wTKD44xT9BEc8lkIKRgz0o4wJACka0AwQNKMBlkmDQld6gaVQgBpRiKgtTlBRmyBDmuMcBSzsEgwR - fHSgErjbCBDBBCYMQlikEMJKFfoAdhwkA5jYZj37wA7dJUQA5MDlOFuxzoVA4qcDTcNCBiGCsopgEAMZ - w1IVqoGaDgQdSIgoAXqwgpacg5/jlEfrClINCIA1EYdJBhPMKgImFCUDUVjrFVBgnWJcIqKAIINbKeKP - TY4THMBAT0DBagBTDIQChC3rM/ex1gI0QZgJoQAanhDRQ/xidgIQ/uc0dWquHnBWFc9sQGhFgNoAJMMX - pQXC25bBhxvU8wZzECNCatFPcXT1UAUAqxdiYK3dNoMgwYjFWstQAooEYxARhRlFiJlLvVaOFDzgLAA2 - pdvQguB7OigtBoyXjHSwgpuXqBx5qUmO2UXDC2DtAjSwtNv3EmQZqVjrKqZhkVQRwJeXMB45OjmKcfis - IhTYAWdVsJT2EtbABOFAaScw2YSMwAjGFaUaLFILeIxCHCO7yDU4a4HrEti9BqEAIEpbDYyQwhyHuEEC - MHHKozQAC2CFwAIO4mGzgpggSigtFHrb4IYU+SiY4OwGJtvksj4ZbBMobSOs+pwRBAGsItBC/lAKjBB0 - rGKtqXjGH0nhBM5+4Mq7YjPJIlHaPOCZReb4Ali5QA/56PkgyyDAWmWRBfxRAAqcTQF3Dn0Qta4VDyWG - EDY4WwEqy4nSOd5Bac9gvwYoAqxgYOykcayQJFxhrZ2u3w8464NMC6TLIviyQUixh9IugcxHQYdPf6oA - GEwE17o+1APWigWnsigZuOBsCMyFbIpcoLQ8AOdRlBAGsG6hrscGdVCesFZaSKNDyYgCZx1RkWpT5Bql - 5QWwccKMbv9UD7auLqsnkoxbrDUW87YJKZgx7IF6QQx+EXdCojGLpd4g4PlrQHo/2ofZuZsipIjvSikB - cZsw4weoSAUdS8B5cYpQgAhQgMIj/vybkWo7z/vGMEQAyZTdeprmPsF1vnFOkgYYg7BMYDnPe44Iwg6i - 40OHjAmIyoQJNC3pP6EAM0AwAJPu/C4BAQAh+QQABQAAACwAAAAAZABkAIf2hub+/v7+/P7y4PD+TOT+ - HNz+FNz+DNr+nPD8INzy0O78xvT8pPD29Pb+bOj+Htz+Gtz+Dtr+rPL8eOj8+vz6+Pr6cub6SOD6sPD4 - quz8+vr4+Pj4eOb4bOT4YuL4mOr8+Pz8+Pr49vj49Pj2xvD2muj89vz88fr61/b29vb02PDw8PD0vOz0 - tOz25fT09PT04PDy7PLy5vD08vT08PT08PL+XOb+Ftz8WOT+fOz+LOD8pO78ou78nu78nO78mO78lu78 - jOz6guj6YuT6oO74huj4duT2kOb4ouz07vT2ruz0wu7y2u782/b8gur6muz6luz4muz+cun8e+r4pur4 - mur2qur8fOj8euj6euj6eOb6dub+UuT8ROL6WOT6UuD8NuD41PL8Mt7+NN7+Lt7+Kt7+KN7+JN7+MeD8 - Ktz+Etz+ENz03PL+Ftr8z/X6vPD2z/D+POL8yvT8yPT4vvD+Q+L+SuT7tfD6tPD8sPD6svD8rvD8qvD+ - qvL+pPD6kOr6bOb4juj6fOj6eub6XOL6VOL+hOz+gur6h+j8lO76hOj8hez8fuj+jO78fur8iOz8aeb8 - TuL8PuD8Pt78LNz8iuz+k+7+mu7+Y+f+VuT8luz8YuX6pu74puz2tuz6nOz6mOz8mOz6ru76oOz8XOT6 - qO72xO7+VOT6qu74su7+svL6sO7+vPT8SeL4tu78RuL0yuz4vO7+v/X4wO78/Pz+2Pj8OuD8QN78QuD8 - ROD6cOT8UuT6cOb8VOL69Pj++vz++Pz67Pf+5vr8k+z6w/H6fub+y/b8juz6fuj64vb05PL85Pj6rO73 - 8PX+fur6zPL+6vry8vL6+vr89Pz63/b36vb83fj42fT81fb7vvL4zPL8zPT4xPD8tPL4uPD8svD8rPD+ - qPL+uPT+xfb+/Pz+3vj69vr68Pn+6Pr7xfP+0ff65vf85/n48ff60fT+9vz07PT4xvD8sPL6o+z+eur2 - 2vH+OOD+SuL+jOz8cOf33vT8v/P40PL7t/L6x/QAAAAI/gADCBxIsKDBgwgTCqRQYUOKDRVoKZxIsaLF - ixgFVshjyFA3ERIzihxJUqSqHChzdAtZsqXLlwIMpcxhiOXLmzgtUpiJkkLOn0ARVuCZw2fQowopULBZ - UQRRoxmZIq1IYRsDP26kKnTKE2pFAawaNdpXYerFBWLFLsDIdaZXit0cyHXQ761ZhJbSNrJ04mLblHYT - rpMy1wE9aXcp5tWryu9Ti9J+FHYgBXFihar0il1m8W9Pi2EmO/BxeWIwzY38BDbouWjTRZOlsCs9MRtq - N00fU8wmOg/tib6AaAZigmLr1QSb0ZtMz9fviW5QyzKuOyEFPqLTPZ9YwQ9qaBOP/k+kRrhwJeTbBVJD - LUHrQPEJK1gS3ST9RAp5UNdPCB/hM9E9uGffQMEslhYCyPXHmhOxBTPgRLTsg5ox/FVXEC3eiNbNgxT5 - colmlkyDkIIEVVPeXM4Ux+FE2KDGyogWDiQNA6LJQdtSI0njnWazsRajQE2Idgx6CNEigAAYCeCGHwiw - 0ldGyaDWh1QkBlBBEKJRkxEF1ASBiQN7IFkRNgiUWeYCKurUDWoo+NiVQfGIxgBGtFRDBCY25GlDPxap - YqaZfmBDJEEFanYJCAWR2M4UsZ1zUQMZ6qmnE4MG4OefZkrAmUVoabYWQQrSoodo2VhUAT8TSCqpE5ax - iCmm/k42hQCIT2pkoYmTHVLWfS4co6qqb1QagBuvYmoMohOhgNqGAw3FE0sU9CCaOxA2wwmev+aJCQbC - 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 - /Gjm/Gbm+mjk+Hzm/lbk/kjk+mLi+lzi+krg9OLy/Dbe+kjg/Cre/CLc9tTw/CDc+sXx/hLa/hDa/g7a - /gza/gra+Mzy/jbg/MHz9s7w+Mbw/j7h/kzi+MLw/Lby+rzw+L7w+rTw/qby/LPw+rLw/K7w/Krw/qbw - /p3w/KTw/KPu/Ibq/HTo/Gzm/J3t/Jjt+qft+pns+oXp/JXt/n7q/nTo/JTs+n7m+I3p+Irn+nLm+lDi - /IPq/obs/IDq+nzo/Hzq+ojo/ozt/pru/HLo+mzk+K7u9rbs9Mzu+Jzq9qTo+pzs/l/m/FLk/lTk/Dze - /DLe/Cje+J7s+KDs+qju+qru+qzu/lDk+q7u/E3i/k7k+Lju+rLu/kzk/Eri/krk+rbu/rjz9rru+qXt - +Lru/vr89NLu+L7u+qLs+qDu+lLi9sbu/Crc/Dje/Gzo+pLr/DDe/Hbo/DTe/Hbq/Hjo/uT6/Hro/uL6 - /D7g/PL6/tj4/iTe/ELg/iDc9rLs/Ebg/h7c/Irr/hze/hzc/Ejg/Izs/hre+tf1/Nv3+Krs/rr0/I7s - /r70/JDs+Kbs/sj1+O73/jTg+Nby/Mz0/kji/kbi/q7x+n7o/pju/Fzk/Ob4+oDo/Pr7/Nb2+fT59vb2 - 9vP2+uf39u30+OT09tny+s70+NHy/MT0+Mjy/Lvy+r3y+rfw/LLyAAAACP4AAwgcSLCgwYMIEwp0x6Ah - gwkKI0qcSLGixYEe4FCi1A/DxY8gQ4ac9ajko33uRKpcyXIhJZOPvqVsSbPmxAkwS860ybPnQAw5H+30 - SdSgu6EUGQRFSpFp0YgCPqBD98FpQqU5rSIUoObGATVanxYsd6DsgXIWscIMa3BWjrc5UIqVqMfsgUrL - Kqo1yZbgPENwczyCOFdhJbsHZuldSnHCrsA5DPUVOwvxAXZJGU/MBjnHgsIRxVnGM3mvzokMrnXGDFph - PssfUGuOKKZzndYR4dW1q8eDRNNCJYoDHPhRXtwKP1jO93s2QncWOqNFrnACHsvHrzo/WK/zjcm4Yf5Y - hhMReNgJlTproy7RXWXEMBSaV3iv8x7wyMUdtktH63yESkFmiDjsURQObNpl9Rw/ne1TIEUYLICYHtkV - 9J9Bw0H2iEcPbmaZGgBut1AEnd3T2lEgTYCOZaxZKGIAMHSmDn4ECSCARVGhQ4caFVI0j2X+MHXhQBMU - 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 - 7vai6vaY6PTI7vS+7Pry+Pfi9PT09PLy8vTy9P4Q2vrX9fTw9PTo8v5Q5P4u4PyS7PyO7PyM7PyK7PyG - 7PxM4v5l5v444P4O2vxy6Pp+6PpY4vig7Ph65vhq5PiW6vaw6vae6v4Y2v4i3PyG6vyA6vx+6vqM6vqG - 6vam6vPf8P4g3Pxq5vqI6PiQ6Pxo5vpo5PbC7v5O5P5C4vh25Pw84PpO4vwy3vpQ4PfM8f4i3vww3vwq - 3vTU8P4c3P4a3P4Y3P4W3P4U3P4S3PrA8fy78vLO7v484Piy7vqx8Pa+7vyn7/qs7vqm7vyi7vqk7v6V - 7vye7vyc7v5w6vyW7vqQ7Pxt5vyI7PyJ6vxu6Pxw5v526f6A7PqD6PiC5viA5vpw5PpW4vqA6P6G6/6O - 7Pio7PiY6va27PqO6vqQ6vqS6vqY7Pxg5Pw+4Pw44Pia6vqc7Ppg5Pqe7P5W5f5M5Pqg7P608/xX5Piu - 7PxP4v5K5P5K4vz8/P76/v7E9P5G4v5E5P5E4v7c+P4s3vw64P4q3vzy+v4o3v4m3vxA4PTm8v4k3vxC - 4PxE4Pil7PxG4v72/PxK4vii7Pzb9vrn9vfD7vx66PbY8vrN8/p45v7O9vbw9fzF9Pyy8P6w8vz6+/r3 - +fzW9/r0+vjn9vrg9vTt8/Lq8PfQ8vTa8PrE8vy+8/TO7vi67vq48Pyr8Pqu7vqo7vym7v6g8Pyg7vya - 7v649P78/P7J9v7k+vz1/P76/Pzj+Prt+PjA8Pfb8/rV9P7W+Pfy9/zL9fy38vqs8Pyi8P6u8v5w6Px2 - 6Pzf+P6x8/zo+fTk8vTc8vq68Pyu8PjG8Pq+8PbK8Pq88vbo9Prb9fx06Pq28P6e7v638/74/Pbs9vrj - 9/699AAAAAj+AAMIHEiwoMGDCBMK7ERBg0MKCiNKnEixosWBHszEibMM4sWPIEOC9Heg5IFlnUSqXMly - YRyTB+KkbEmz5kRgMEvOtMmz58BgOQ/s9EnUYKehFIHmREqRadGIAoRduiTMaUKlMK0iFNCrWrVeHp9K - 3DWt7LQPFrGa1HpwWZ+3fVCKlRjB7LQIpyqq1VmxFty3M4DNjWi3rD+9QdkSBMbmbx9Cggcn9Fd4GjuK - GhJTZOG4TwTJCrtVXqdYYOalEzXE6XwZdMJelcOl1ixRWWczrhUOq2s3gjaJp7NKNEHD8YxhuRUKq4wN - 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 - N4yoAT3QxAtLMMAwjVf2Ak1oAjTCXRSGRPnZEnGav99oEECfm+YqAbSOcQ4SCuCgsGhgOc895YfConXo - PAEGL4KKhiIUFek1AUYDUDAAL+78KQEBACH5BAAFAAAALAAAAABkAGQAh/aI5v7+/v78/vLq8v5K5P4a - 3P4K2v6a8Pyo7vyO7Pwu3vTC7vy+9Pyk8Pb29v5q6P4q3v4W2v6q8vym7vx26Pz6/Pr4+fpu5vpO4Pqs - 8PqU6vz6+vj1+Ph45vhs5Pia6vz4/Pz4+vaS6Pai6vac6vzl+Prw+PTk8vTW8PTK7vfk9PT09PTa8PTy - 9P5a5v4c3Pyi7vyg7vye7vyc7vya7vyY7vyW7vyS7vxa5P5w6P4v4P4Y3PyG7PqI6Ppm5Pqg7viG6Phw - 5Piq7Paq6vTQ7vTu8vyW7PyU7PyS7PyQ7PqY7PqW7Pic6vx+6vqW6vam6vvB8vx66Px46Pp+5vTo8v5W - 5v4Y2vpw5vh+5vTm9P5M5PxK4vpY4vxE4PpW4vw04PzS9vwy3vbT8v4W3P464vzK9PnA8PzE9PzC9P4/ - 4fzA9Py28f5G4vqy8Pyq8Pqw8P6q8P6h8Pyo8Pym8PqM6vpo5PiQ6vqE6Pp05vpg5P566vqI6vqo7vyC - 6vyA6P6A6/6K7vp+6PyE6vyG6vyI6vxu5/xW4vxG4Pw44P6Q7f6c7v5i5vxi5fi47vbA7va07Pqe7Pig - 7P5Y5Pxc5Pik7P5W5P5U5PxT4vqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5PxM4vqy7vi87vxE4v699Pz8 - /P7X+Pw64PbE7vqk7vxI4vpu5Pa87vrY9f4m3viU6P4k3v76/v7p+v4i3P4g3Pi07vp45v4e3v4e3Piy - 7vxY5PTe8PyN6/7F9fTg8Pz2+vfv9frK8vTk8Pzs+vri9v76/P464Pbe8v7g+f72/P7K9vLy8vr6+vj4 - +Pzn+vr2+vfp9vrE8vza9/jU9PzO9vjI8PzG9Pu88vq28Pys8P6o8P629P7B9P78/P7b+Prd9v7s+v7H - 9vz2/Pfx9/rS9Pzx+vrm9/jb9P7k+v74/P7Q9vjM8Pq68Pyu8PjM8vyw8Pyy8v424PzW9vjE8P5E4v6K - 7P6a7vze9/bK8PrG8vq+8vy+8vrA8vy89Pr2+AAAAAj+AAMIHEiwoMGDCBMKLMXMQjMLFRRKnEixosWL - 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 - efNU41wQa1j2DlPmUDRW64MlUznM0ImCZddxvGdwIsgoqI6fE/INLWT1AZbuTipW24NI9xfT6qmBZUMh - VJ6YgAxNJQMVdjPqhJigCmZGc4NKcYfVPgG+vU5IGyyrBDv3pBV5luclUr2lZPfnAVn9BF/rU4oHrBYV - k7E2QoJh2T4IyALRhsAVMitqC7etD1nVRK1ZUopm4LqhOjhGXfxLEWI816IumPdKsAfehiohtPymSCpu - WkgBNYMSOHABDZGS8IlU4A05yIGOOcQQYSo6thipgAUzXO3GolW85DN5rWRJjnKVMIMedGWDs1u+PDrQ - lRc030kFxhFTNvBirDm3nQOQGEUU/yUgACH5BAAFAAAALAAAAABkAGQAh/Z+5P7+/v78/vLY7v5E4v4i - 3P4E2v6U7vyU7vww3Pwe3PLO7PyY7vj4+P5m5v404P4G2v6k8Px26PqM6vr6+vo+3vqk7viW6vr4+vr4 - +Ph+5vhu4vhk4viu7vaU6Pz4/PzR9vaK5vbA7vLy8v4U3Pjv9/b29vT09PTm8vTM7vS46vSk6PbY8vLq - 8PLk8PLe8PTy9PTu9PTW8PLu8v5U5v4y4Pxc5Pp65v506v464f4S2vyO7PqU6/pW4vig7PiE5vh25va2 - 7P4X3PS+7PSu6vyB6vbO8P5k6Px66vqQ6vna9Pii6via6viW6Px46P4g3Ppu5v5o5v5I4vxI4vpw5Pw2 - 4PpS4v4k3vpE4P4i3vw43vwy3vwk3Pwi3P4Q2v4O2v4M2v4K2v4I2vq28PfO8fTS7vy+8vbG7v5C4vjC - 8PbE7vi87vi27vyu8Pqw7/qr7vyp8Pye7vqo7vqm7v6i8P6g8P6a7vyc7vya7v576v5r6P5y6vpe4viK - 6PpU4vpK4PqH6fpy5Pwo3PyY7PyI6vx86vx66P6E7PyA6Pp+6Pp86Pp85v6L7P6S7vxu5vxO4vxC4vw6 - 3vw23vyK7PyM6vqa7PqS6vyM7Pik7PTG7Pau6vxk5f5b5v5a5Pqc7Pxf5fqe7Pqg7Pqi7Ppa4v6p8f5Y - 5Pim7P609Pio7Piq7P5O5P5M5P5M4vxM4v629Pz8/P76/v7G9vw74P7Q9v4r3vpo5PxE4P7c+Pzu+fiQ - 6vxQ4vxS4vxU5PrK8vxW4vyU7PTs8v7m+vxW5Pxa5Pbu9fyS7PTq8vzZ9/Tc8vp05vp25vjj9P76/PTg - 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 - hBoOUCsnnlERbGqTlYewUSgwSYsfGCN20lDEX4NgEAIX1sACycZif+Hcg1DDHvXsZDZwyIMc0EIPLCjx - Qdrx102AOHntvWYhFquNj2QnCjgYQ+xeQeQ6SsMGfy3DQTxc1hsHQAmLRYJ5tUWBhzjGDX/9AV+ZTFYn - v+IXi/WGIEsgha+iAQUI4TL+AZwcAFxwQq0OoKr5ePDXJZxRzWwOgBsWawGaygcb//3pLmBwvRznRw9q - 7cQyzJfYv4pAIXhWSC8WO4Epd6cbf4XClCOdEGkwVK2fHV8DShFdFkSE0wnBxmJxkOfoyOGvlT61oRHy - ijssVsjIwwUBvkoAd0gE1Xc5ApybqqBXTOCvqdAQsBMyhsWC4ps0UUKgQzqMVg9k2QjBwB4WizD5vMIR - fz0DRbCNkHYsdhL/acC0DSoBS1971kKZhIKhfaNdhzQH1WAXvBUSjzer9Aj0XkkD0BvSoOq7wBZ5r0o1 - mW45fGITbxAfuTsdDiQgYQx+TlwdJ97phggyNLt198ctQ0KBkI98J9IgbFlVkfGTrygJhf2Fy3UijXcQ - VRWW0OfMVRLDE4zgBCn6T0AAACH5BAAFAAAALAAAAABkAGQAh/aO5v7+/v78/vLe8P5M5P4c3P4a3P4M - 2v6c8Pwm3PLY7vzE9Pyi8Pb29v5s6P4e3P4O2v6x8vxy5vz6/Pr4+vpu5vpG4Pqs8PqY7Pz6+vj4+PiE - 5vh45vhq4via6vz4/Pz4+vj2+PbC8PaY6Pz2/Pzy+vrh9vTU8PLq8vTA7vSw6vfq9fTw9PTW8PLk8P5c - 5vxW5P587P4s4Pyo7vyk7vyi7vyg7vye7vya7vyI7PqC6Ppc5Pqk7vqe7PiE6Phy5Pik7PTs8vao7PTI - 7vS67Pza9/qa7P5y6fx86vx76fie6vao6vx26Pxy6Pp66Pp85vp05vjV8/Tm8v5Q5PxC4Ppa4vpQ4Pw6 - 4PbO8Pww3v403v4u3v4q3v4o3v4y4Pwo3Pu98f4g3P4U3P4S3P4Y2v4W2v4Q2vzO9fi/8P484vzL9PbK - 8PzG9P5B4v5K4vq68Pq08Pyy8Pqy8Pyv8P6o8fyq8Pym8P6g8Pyk8PqS6/pm5PiL6Pp86Ppk4vpY4v6G - 7P6C6vqK6fyW7fx+6vyC6Px66Px06PyA6vyC6v6M7vyG6vyI6vxr5vxK4vxA4Pw43vws3P6S7f6a7v5i - 5v5W5PyY7Pxi5fqq7vqg7Pim7Pay7PyY7vq07vpg5Pxb5Pqk7Pi67viu7P5U5Pqs7vqw7v689PbG7vi+ - 7vxK4Pa67vxE4vTQ7v7A9Pw+4Pz8/P7V+Pw83vps5viS6PxM4v76/v7l+vxO4vyU7fvE8vxQ4vyR7PxU - 4vTa8vyO7PxU5Pqy7vTe8vr0+PqA6PrM9P7K9v74/PTi8vrr+Pzn+P6A6vjc9P5+6vbS8PjI8PLy8vqo - 7vrQ9P649Pr6+vz0/Prl9/jx+PT09PXq9Pzg+Pja9Pbk9PzA8/zU9vjC8PzM9vzI9Pu28vq48Pyw8v6s - 8vys8Pyo8P6k8P7D9v78/P7f+f7p+vrJ8/r2+vrO9P7R9v76/Prw+Pzr+vji9Pbg9PjM8vrX9fi68Pbw - 9fqm7v566v444P5I4v7b+Pj0+Pf09gAAAAj+AAMIHEiwoMGDCBMOnCCt4QSFECNKnEix4sIzESKceWix - o8ePHs+MGznOHciTKFMSjEByXASVMGNSnNBypMybOA3SrJmzJ86dLV35HEqwFjFi2CwCJSm04quM3DgS - hZiulNVSSWfWHNd0ojcEYBGc6ToV4bmrpViR0FqTLER4d8IioOO2bEFWaEuZnLh0ZF2ErkrJRXDnr12B - r/KWijeRwlbDBrENRlDqcMISiokJkOi4Ledxg++UsJywiOJanz1H5DaZGOmEH86iPSc1YeegEYOZG0zn - w+uEtRTzi3ibKURXCyYX+Z1wAjHFoxUW9wvx2OQIm5kjjKd4r+3HCif+gJuMTLtCd4rVSQefUPLgyuYT - TsOLVvN31Qcp0AkdPT5Cfoqlcx9uCJ0xmTf+hSfbVef4lh97BsHDAG+1DQUZRMHl9QpC03F1kCusKGcX - BQ00IE1HrjyXV38EdfgXMtddGBMFPjwghh/MVBgRZnm5phOEA0kTwWSMVeTKMREkIggbFOVxwJMHjAHF - ChYllpd6BfXlYUFFTLaARdWUksiYY54hkTSOQAllGB6EQBE79DGY3UJABqDfYAxMQxEFwwhCJpl0RTRB - mmpCecUpJ0qETYBZ1qnGZN5BNIEJd/z5px06HlRJoYXuoIyMAUywoFVqEaRlQXANNk6mBR0pjqX+lo4l - 0QSBiMEplGIIU01EyCi2IZ08DRTYZFkpFMw2kcBKZiTfgCpsCzvcCmUCQFCgkCtnQAdsSwRRM1k0EFEA - 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 - WOT+SuT6auT8Ot76UuL6ROD6zPP8NN78Mt78Jtz8JNz+ENr+Dtr+DNr+Ctr02PD20PD+NuD4zvL2zPD6 - wPL4xvD8uPL+PeH4wPD+SOL6uPD4vvD+pvL8tfH6tvD8r/D8pvD+pvD8ou78luz8juz8iOr+nPD8pPD8 - n+78nO7+fur+duj8lO76re76ien6XuT4jOj4eub8gur8euj8cuj6gOj6dOb6VuL6TOD8iuz+huz8fOj8 - gOj6fub6muz+je3+mu78ZuX6mOz2tOz0yOz4ouz4mOr2tOr8QuD8ON78LN7+X+b+VOT4qOz4oOr6nOz6 - nuz8V+T8U+P4quz8UOL+UuT4rOz6sO74sO7+TuT+TOT4su74tO72vO74vu7+uPT+/Pz+x/X8QOD2xO76 - qe76pe7++vz0yu7+2vj00u76YOL6YuT6ZuT8ROD83/f4juj8RuL8SOL+JN78SuL+Itz+4vr+INz+Htz+ - HN7+HNz+Gtz6eub04/H87Pn++Pz62vX58fj8gOr81vb+NOD45fX8wfP+RuL+rvH8lu7+mO7+1Pj8nez+ - tvT8+vv8+Pz69vr39fb29Pb66vj8zvb28Pb06vT43PT24vP60fT21vL40/P6yPL4yvD4w/D6u/H8tvL8 - svD8q/D+pPD+vfT+0fcAAAAI/gADCBxIsKDBgwgTDtR1bsI5XQojSpxIsaLFgQLI9evH7dzFjyBDhiSX - q2QuciJTqlxJ0GTJfixjyqwowGXJmThzGjxnM5fOnwnPqWPA4CJPm0CTEmQwpEsXKkUrHnX5UVhJYRCV - RtSFwIBXAxmyTpxq8qI/O2jtoNSq8ByNrwbKwJPa0+K1tGjDiWW70wxcAz8mUCR7k6KuXXjR7uVLUBer - vwY4DK5LcUViO7sYJ1Rz5m8YEWMpS5xw57IHzQgnaIDMKTTSidwu+0Od0AS1v9RGSCTsU6KHcInDoaON - 8FwIyFg8thWt0MVlYcQTlgjz9wy9iLwlsrsMQUD0hCgg/ueIWpz5wXP9Ll/7nlBdJMi1lr9OiOxyZvYJ - FfiFSwp0+fnnmWMafgkxsAhkiiw2UHYJxZbYbAQm5EQCfxXgzX9UIfRbcMOxpeBEunQAGQXKFcSgQYdd - hgxfEwwl2EUkVPDXA/acZ95A2yUGE1sYIOKANInMUyJFREAmCnkL3hjAORCo99E15hwAiAsVcVLAlQVI - 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 - 9PL09Mru8O7w9MDs9Lzs+fH49tjx8/Dy8uzy8ujw8uDw8tzu8PDw+tf1/lDk/i7g/JDs/Ibq/Ezi/mfn - /jjg/g7a+pTq+mzm+mTk+lTi+Yrq/hLa+H7m+Izo9qzq9OXy9MTs/iLc/Hbo+ojq9qDq/iDc/HDo/Gzm - +oTo+Jbo+Ijo+Ibo/GTm/GDm/k7k/kLi+nDk+mrk+l7k/EDg+l7i9Nbu/Drg+lLg/iLe/C7e/Cze/h7c - /hrc/hjc/hbc/hTc/hLc/gza/gra/gja+rzy9sjv9NLu9Mzu/jzi/LXx+Lfu+rDw+LDu/KLu+qzu+K7u - +qju/pbu+qTu/KDu/Jzu/nDq+prs+pLq/Gbm/Gjm/I7r/H7q/HTo/G7o/njp/Irr/oDs/Ijs+nvn+IDm - +mLi/ofs/Hrq/Hro/o7s+JTq9q7s+obo+ojo/HLo+m7m+Jzq+pzs+KLs+qPs/Fnj/ELg/Dzg/Dre/lbl - /kzk+qjs+KTs+Krs/rjz/FDj/E7i9rbs/kri/kjk/kji/kbi/kTi/Pz8/vr+/vr8/uL6+pTs/jDe/N33 - +o7q/ize+orq/ire/ije/ibe/r70/iTe/HTm99/z/sH0/sj2/ETg/Ov6+un2/Ejg+sLy/vj+9uTy/sr2 - /Eji/Eri/tT4+uP2/Lzy/qDw/IDq+sny/rDy/Pr7+vb5/Pj8/ND2+vT6+Nj08/Hz8u7w+t329Ozy9Nrw - +sDy987x/Lny9sDu+rnw+LLu/KLw+q7u+qru/pzu/KDw/J7u/rz0/vz8/uT6/OT4+N/1/sX2/PP6+u34 - +Ob2/tr4+uX4/MHz/qLw+tDz+L/w+LTu/q7y/M31/nDo/KXu+OT0/IPq/rPz/Nb2/K3w9u319N/wAAAA - CP4AAwgcSLCgwYMIEwrUMA3GABgkKCicSLGixYsYBabgUaZMBWsSM4ocSTJjqgkGUhrYEbKky5cwKZhR - aeBHS5g4c1psQNMABA06gwpFuKLnz6FIFTZroOFmRWpGGySdWpCCjiuYmgC9CJUmBKkY29nS9SoV1Yqp - jmxYu8GHWYtdVX7FuI+ZXWbtzlJsVoTthjDSuEa9qE7fXWag3upF2EyM3w1WnCaMm3Iu2l2HmelbrDDV - pccb5Fk0Mbiiu8zMdnFWmG3M40zOKpL2CnZiM1uZ9albnZACB9CUZJee+Aq1Md4Kh3B5zMUXxdlyayd0 - BiozqFXIEzYTAdpQs4nQK/5LRwgNNavsCkloeSwmBvjhCNehtoV+IgrQPSQTDJ9yfMFU6aDWS30KNWAB - aG4oxJ8B/hF0DmrDEDhRFmE89kAICS3YoEDNkJPbbhL2dghoGShWkIYJtWNciBO1oMBjX7CDEIoHRWPY - YddxZuJFqSwAmiTfGUSjQfWYxxkFTAWJUQO8PEZGNgcNSVAvqOlyZBAP1DKBNDtSRARoxWxFUFE9+deM - LgKK1As5yUQAzUWUFCBnAbcw4dxFGgAAmgcGkUmTfw9m9iZG0YyQzKGH7lNRM1LMOecDlcRm0QtgPOZF - CgX5qdJ4IFR3mD7PXETBPhEgiqg+XRqUSiiOOkoKMP5KKkTBB6A5YaKm/RVUV2Z5oXUMM6aaimpFd7Ta - agLopFoQC108BgY6BE3TkwFiBlBYZuQIUNE6tgQbrKIVCSABLcbOSYssmHYmBGgTtMRTTzcNg5o5FDlT - DzzeIgoPNMoelMo1FZQ7Jw4e6DdQAxeABsxAGkzbEpWZpWNbDKXme6guIGakwQm/CCxnKMvEWlAJrvl1 - RUjN5EBTDkrillnGqrqjj8WH6nPOSyQg8YDHBVCBT6oaUAFaNwMlQNMOQZqD2qAHpaALzW22oy1MqQwB - xC0ez6JDugUNUcBjR7wVTQI55LBDCm8ZY12oBjkDDb4WwzMMCEJRoIYVPLMxj/5Tzejw2BsDIdnQNA28 - pfRh5xXUzCv0QE3OgEk1MMoVPF8Sj4kgSMFWKHRTFKBd6UwtUCq9gAI1M4lTlcoKTcTiMReG+KLYKhEo - E0HnFbFiTOoCRZMO1BEY0+9QzbhgBBceP0CJpDkJYEzFFqcD82oarNFD5cy/JIDpNIMCOYENOKKFx/zk - 1A7N9JTFYgCpWBPELOXWkNMw+e6L3foDNXPNDsaWj9P5wcIY/g6igTTYYE4OYBtMtneq7w3wIBTQBgJA - ITqcCIAV0ODdAzfIwQ5epBkUCKEIR0jCEpowhCJDCghPyEITNkMxzegHE9jwgBra8IY4zGEOcVCDaCRF - AP62qMMBhkjEIhrxiEesgy1UEQAKMEGHUIyiDmswvJLYAolYzCIS6UMBGkrxi1FkQwpxIkQtmjGLdUhF - F8HIxhyyASmpKOMZ51jENDajBm3MYw0LkZQr0vGPB7CFWUBQCC/qMYyFYCJSnhFEQJpRibhrYgNIQMlK - WvKSmKwkU6qIk1R48pOgDKUoR+lJD5rylKgM3DvwgIYxlsR5ttgHJ1nUjHhAgQC4zEMkXSKAZBDil3Wo - hivXdwxl4PKYBABFTqDxy2YSIgKy22AK9HAKZB4zHDkBhTObmQg0YGh9zeDGJqyJTGQsc5vOZEQ8DMaZ - ZsQgAeREphEq6JJmwAOdzv7EwzGGSRVf1CCex5SCB2I1y86MIBH4bOYb1sEbZ9iBFAAlwBRg0Q+CgEAb - eECGAm0zAmYMo4LqaENCf5kIYHyTKs0AxiUiSgBAqOAmIFCEDGb6B3oeRAB1yINO60BPd8RhpIQ4QAzY - +ZJUSCMSLHXANqolkDfM9KkSEwgFNMAUCiimHjrN6jtUtY8DADUO7CjoRdbhhylElBSeaNA6NvHUmdJj - IM6Iww1u4A0FtiGrOlUE1wjSjHcgdKRt2KtLnCEHKbD0CEMYYyoQ0NaZjmAgcXCAZB0Aj4FAA686BYcr - 1cEPoCZiBEzNSDOW4QCWLiEb7KxGY2VgBMXcYLIOuP7BQFIxCMzmAVoJOQcegFqHGPATIalAxyJYugk3 - bIhDMm1sYEYHW8kqqRq2RQBRUwENRgAVmhjRADUjWopJVHQiwFitH1rSjOY6oCWpSIZtoTQRECDjrwnd - 6qLowFJl5OK3AYjGDRq7CWHw1bzV6oUgMJuIkypkHREA6mMpQoEbABQK2QgtQuiwWjqYiALmHc9dMcuN - gr6CAQnFJoP5QE4ZwOG4BvEFW9t6AxJUJcMFcQYhMCuIO6FlBINAJx4sIgdkokIH/RBrKvywWtG8uLn+ - WYZt74FfgkRDm86sh0VSgQEZlOIP6GgyQWKwWkWEFsNIVtUBbFsNkfgiAolgxP47xMo+kjQDEKtVAQRh - bJBX2JYGEt7LCzmDhtVKd85hPsg9bLsMVKbAAY11wHcNAmbYbmgdA8YrIQS7wTisFg7KavRkj4sG27aB - zbw5xoqfmoDjalqyxwVBIjALCBlxMBU1WG2he0Nn8tj2DkStTzxWm488S7XW/mKAbd3zQAoEgr+5mMip - HYDiALjDtoPwNXrasFpK5HrZzQ4AP2y7MPytA9FtVTSDgY0QdSgCs3+gNHpSoYfVuoGT2K4IMGzLD1Aj - pRqjnqkSst1EcjNm1Zg9RohSkY/VsnfcgaZIDGy7YwlpIN8yGESuA+dv4OKBxiHSALhnugl8iKriCPEF - ullDRAEKP9V/Fon3RbSBV3NmvA1GEIQ2bCorkAMXG0MEhr2TsueMqJxHIEzlXgAsdBWad+JFF0kzXjvZ - G+w86UqJ7GTjAHWhpCIEd5hrHLJXdapRlQRJClFAAAAh+QQABQAAACwAAAAAZABkAIf4buT+/v7+/P7y - 7PL+SuT+Gtz+Ctr+mvD8qO78iur8MN7u7u78wPT8pPD29vb+auj+Kt7+DNr+qvL8pu78buj8+vz68vj6 - cub6ZOT6SuD6lOr8+vr4hOb4eub4wvD4uvD4kur8+Pz8+Pr49vj2nOj87Pn0zvDw8PD0xu70vuz49Pj4 - 8Pb09PTz8fL27PXy7vD+GNr+Wub+HNz8pO78ou78oO78nu78lu78WOT6wvL+cOj+L9/+FNz8hOr6fuj6 - bub6YOT6muz4hOj4qOz2sOr06vL0zO78juz8jOz8iuz6luz43fT4nOr8jOr8eOr6lur4mur2pur8dej8 - cuj6dub+Vub+TOT81Pb6dOb22vL8SuL6buT6bOT03vD8QuL6UuL8POD8NOD+Ftr40vL21PL01PD8zfX+ - OuL2zvL4xvD8xvT2yPD8xPT4xPD8wvT+QuH7tvD+RuT7rPD8qPD+pvD8pvD+n/D8nO78kOz+eur6jOn6 - cOb6YuT4juj6euj6ou38fOr8euj6huj+f+v8gOj+iu76gOj8fur8gOr8gur+kO3+mu78bOb8VOT8SOL8 - QuD4ru72wu72suz02O74oOz+Yub+VuT8YuT8XOT6p+74pOr6qu7+VOT+sfL+Uub+UuT8UeL6rO7+UOT+ - uvT6sO7+TuT4su72uu74tO74tu74uO74vO7+vPT8/Pz+0Pf8iOr4kuj64vb8ROD++Pz+KN7+Jtz+JN7+ - It7+IN7+INz+Ht7+Htz6zvL8VuT+3fj8hur6fOb6oOz8WuL8lOz83Pb+OuD+OOD45PX+RuL+iuz+wvT8 - luz59fn89vz48vj29Pb08fT28fby7vL8xPL06/T44PX81/f23PL05PD41/T80fb4zPL8yfT8uvL7sPD+ - pPD+uPT+v/T+/Pz+2Pj66fj++v761/X+4/n84fj46/b+yPb04vL6svD8rvL89Pr+NuD+iOz65vb++P76 - 0vT67fj62/b6xvL25vL8vPL8sPL6yvL24vT6vvL6yfT8vvMAAAAI/gADCBxIsKDBgwgTChzB4sSCEyw2 - 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 - bAH6qrUQAmZQkv1qiiIOOiEMOG034mzLDgd1CvHdcYITEgJFnJgcDmzFETjLZw5tOiGITWwD3FyeeXA2 - Afd99GZscNp6MLACUuBslk094onE47SKSHRqgOpXGrBa1gphx2ntUT8/+/UBbVUIshPijh4kFh5zlU8r - asDZDzh42glRw2nhQOmkoIOzTsg0QsC9FE+rdRAuAlArkMDZJlH41BSpx2ldDKANcBYPrK4bvifSigac - mEEI/moljnERdidkvmqFB8IJHFRvYMThCcGHWrGJcDgcYhDdQKlyQwdOkY3ewMuwTQqcRYLxzXBQkyW/ - rrBhLhLr9jbgNK85aAmrg3LnvOQICO0cfi6UVqhgAmSdw1mJflNmLLIpKd9JQAAAIfkEAAUAAAAsAAAA - AGQAZACH9nrk/v7+/vz+7u7u/kTi/iTe/gTa/pTu/JTu/FDi/B7c8srs/Jru+Pj4/mbm/jTg/gba/qTw - /JLs+obq+vr6+mDi+kTg+jre+qDu+Iro+vj6+Gri+Fbg+LLu+ITm/Pj8/Mz19rbu8PDw9oTm/hTc+PH3 - 9u709PT09N7w9Nbw9MDu9LTq9Krq8u7w8uLw8tzw9PL09PD09O/y/lTm/jLg/Hbo/Eji/nTq/jrg/hLa - /Jrs+prs+mzm+lLi+HTk+Jjq9rLs9qDo/hvc9Nrw9NTw9MLs8ujy/Jbs+pLq+tf1/mTo+pDq+o7q+ozq - +JDq9qrq9qDq9OTy/Gbm/iLc/Fzk+mrm9o7m/kji/Fjk+m7k99by/FLi/DTe+mLi/ibe+lDg+kjg/Cjc - /CTc/CDc/hDa/g7a/gza/gra/gja9s3w+rrx9M7u/Lvy9sfu/kLi9r7u+rLw+Lju/K3w+qru/Kjv/Kbu - +qbu/qLw/p7w/J7u/pju/Jzu/nvq/mvo/nLq/Izr/HTm/GHl/Frk/ITq/oTs+n3n/Drg/Czc+nbm+lTi - +H/m+nLk/Hvo+njo+KTq9rrs+JLo/ovs+p7s+pTs/pLu+nHm/G/n+JTq/Gjm+pjs/lvm/lrk/F7l+qDs - +qTs/qvx/ljk+Kbs/DLe+Kjs/rT0+LDs/E3i/k7k/kzk/kzi/D7e/rfz/Pz8/EDg/vr8/sv2/Ebi/Dbe - /jDe/GTm/OL4/tr4/izg/ize+lrk/ire/ije/uL6+J/q/HTo+svz+Oj2/PH6+pzs/vj8/NX2/h7e+OL2 - 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 - MqcG+qxBNEQVADhoFg7oUa9up5LWxH6zIrRQrTzJeN9uKiEOarGIP74KVj60VcCfnYoGKJFYtl5EAMMY - LjUZTBFrCBMUGDBBa3HLAM2upiAYJuxYEKvWeuT0d9M85jAsIgA5KEETfyjGjQWSDs3+YSwxHuxYWMGN - 0lorI+NYhR7wAIIV47Z5hNDsPg6SZPYepLeJxQN/l4LU0khDsxiITv6XO/ON0ppDlMkwL1jRi5A1I8S9 - iaUEXTfoY7/eY012Rog8SvsOK0vnHBQeKh8izOX1kuaPHTYuAFlxAM3arM6OVgiNlyqHIZdmGJqVwG4C - PZ6XJvZEAKQAH/zqAG7hJdOoLa03PL0WNmjWDr4idULYUNqH2Q8YfvCrH9ra6AFTBB3vVWsk9nyjOmj2 - HZuC9URAUFo2qI8WDvDrIESla7wwoLT+MJIENDucaBu7IukoLYkbRIFsg/UIOe22Qj7R4RtRINhDpTNV - pE2RceTZ3v4d6v72fW6LgEOtB84mrwkxjZamiN8UYcU68EBlQ2OF1mWEeMRLOEqyaLzjMekyxiRBfiTP - EpYPFid5xOXwWcOq/J8NkENZ5fDRl5eEKQ2wpJVQFBAAIfkEAAUAAAAsAAAAAGQAZACH9ojm/v7+/vz+ - 8PDw/kzk/iDe/gza/pzw/KDu/Ebg/Cbc9Kzq/Lz0/Kbw+Pb4/mzo/jzi/g7a/qzy/I7s+pbq/Pr8+uf3 - +lzi+kLg+I7o+H7k+HDk+Gbi+Mby+Lzw/Pj8/Pj68vLy9qTq9pLm/Pb8/PL6+PT49PT09ODw9NTw9MTu - +PL29PD09PDy8ury8uLw8tzw9t/z+OL09O708u7y/lzm/izg/G7m+oDo/nzs/kPi/hzc/hrc/Jzu/Jju - /JXs+qHt+nLm+lrk+KTs+Hrm9rbs9qLo9O7y9Mzu/h7c/Nj2/JDs+pzs+pbs+Jzq9qzs9Ozy/nLp+I7q - 9Oby/D7g/GDk+nDm/lDk/Fjk/E7i+mjk9OTy/Eji+rnw+k7g/Ezg/DTe/i7e/ize/ire/ije/ibe/iLe - /DDe/Cze9tDw/hTc/hLc/hDc+Nbz/hja/hba/hTa/hDa9Njw/Mz0+Mry/MT0+MLw/MTy/MDy/kzi/L70 - +L7w+7Pw/Kzw+qzw/qry/qTw/Krw/Kjw/p7w+o7q+nzo/Dze/oXs/oDq/Hvp/GTm/Fbk/Ezi+nbm+Ijo - +Ibo+lbi+oPo/ozu/Hbo/pLt/G7o/Jru/Jbu+LLu9rju9NDu/JLs+J7s+Jbo/pru/mPn/lbk+qbu+qDs - +pjs/Jjs/GLl+KLs/Dzg+qju/lTk+qru/FLi+q7u/rPy+rLu/rz0+MDu9r7u/ELg/r/0/Pz8/jPg9sTu - /uL59sru+s30/Gbm/Gjm/Grm+Krs/sn2/vj8+Pj4+n7o/vb8+tT0/Ob49vL0+vP4+Ob2/n7q/H/p+njm - /s72+77x+nrm+u74/PT89vX2+PL49PL09un0+OP2/N349Or0+rzw9tzw+Nv0/NH2+NDy/Mf09sjw/ML0 - +rbw+rDw/qrw/rj0/sP2/vz8/ur6+tL0/sz2+vn6+tz1/Ov69vD2+vT5+Oz2/tP3+sXz+rry9tjy/Lbw - /LDw/LTy/LDy/krk/kri+qTu/nrq9tjw/jzg/jrg/Ibr+nroAAAACP4AAwgcSLCgwYMIEwpEdyLEgBDQ - KiicSLGixYsYBUZrNGYMERYSM4ocSTIjrUk7Uu4Ihq6ky5cwBVYgo3JHmJYxc+qsaKLmjgI4dwodOrCY - T6BEkyKkBQxYUIssjgITWQEdLaUY0QHBgmXIU4pRaxaYehHdplNUKDjAarGJjbc2KGAMq3LsxQqRDOg1 - 4AjYVbYJK0CAa6MWtYt0U9q1mELNXgNmoP0FfLBCLcI2goQEK9UiNEiPDbxZSznhDcw2Olhs0blikdAG - hGwubTDeZcKpSE9kLZbsRBZgQqtpQzvwI9SoKvKu6zvwCNjNZhcvOG0wYQjtKC5X3BzhiyShzf5kn75U - BGpHkxFu/9ndILANsDmRV3giFeZa2Xa3ToiETWhDus13kC2oafFVQesthhA0X8Bmh4AKAWMFaswolGB7 - A9GyAGxVHAghQdjcBhcBASK4n0EzhBHaG+l8qBA6UqAmSkIXIoQOALBFkp6LBkEhC2b8WKDeiQTB4EZo - YzjD40RPoIbDjgLVaJADHMDWS2noOACMdBVBIwRqwxwkZUEqRBBaAhgOhQ4TBOiQSDZQToQLah0alBh7 - Bp2AQWgR0CFSCe7MMs4tF6ECwaEQ6LCEkhcBowxq39hJJC0iwKYFlwp9oMQsr3T6CqEUVaAIoogSwEqJ - CqEg4ls6GFPQnf4KCuSCAqHxcJhFFdwyjqeenlMRLbqQSuondGBqUAWdoNbEqyeiowFsGcR5kDq+8Mqr - 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 - 9Pba8v5m6Px+6vqQ6viW6viU6vaW6PTq8vyA6PqA6Pp45vpu5viG5v5c5P5I5PxC4vpu5PpQ4vw23vpC - 4Pwu3vrN8/wk3P4Q2v4O2v4M2v4K2vjO8vbU8f424PbO8Py+8vjJ8PbK8Pq/8f494fjE8P5I4vi88Pq6 - 8Py48vq28P6m8vy08Pyw8Pys8Pyo8P6m8Pym8P6d8P596v5u6P526PqH6Ppg4viO6PiC5vp86Pp05vpS - 4vpK4P6G7Pqm7fyW7PyE7PyC6vxu5/xN4vw83vww3vwm3Pp66PyI7P6P7fyK6v6a7viz7va67PTI7vTA - 7Piq7PqU6viY6vam6vxk5fig7Pqc7Pic6v5e5vxg5f5a5Pqe7Ppd4vqg7Pxa5Pqq7v5U5P5S5Pis7Pqs - 7v5Q5Pqw7v5O5P5M5Pwy3v5K5Pi27v648/xF4f78/Pa67v76/v76/PbA7vw84PpU4vw63vTO7vxU5Ppi - 4vpm5Pzu+v7a+PxW4vpq5P4k3vuf7f4i3Pqa7Paw7P4g3P4e3P4c3PyZ7v4a3v4a3Pin7P669Pzg9/6+ - 9PyS7Piw7P7K9fLq8vrb9fzK9Pbs9Pp+5vjv9/404Pjn9fbq9P5G4v6t8fx36P6Y7vz2+/z7/PzW9vr2 - +vb29vXy9PTw8vro+PTm8vjf9Pbg9PrU9PjX8/bV8vbS8PzA9PjE8vrF8vi+8Pq78fy68vq48Pyy8v6k - 8AAAAAj+AAMIHEiwoMGDCBMKZFCixYAW69ApnEixosWLGAWSIAQOXBB2EjOKHEkyIzoJDlI66DGhpMuX - MAVOgKTSAZqWMXPqrEiipgMdDHYKHVqQnU+gRJMiRMeAAc6LJo4GNTkhpFKLExKRIpXoacWoNZFeRKfo - 1jgh665aDIWmLZpQGMGqFFsRHaACeAtcYGBV7UF0BNyicQMDqtSLZ5zlLQC0r9+C6NwIRlPOcUK5KelO - ZHBqcYFmUx8jzDEZDRiLIg5XxOS5wCfLogcqkSwYS2iFqcPevtzL8zMlsROiu1DaVcXcc3f/jdJ6Cuzg - ArsFFkzgG0XkmZUblOHAsw7r0Jf+bip94flA7D+1E2SwoTWS8ArZYZnsZttE9JoPAovm+RYG+AqFUJoV - XhmEn3oClWBIa/gAqBAD5ZTWYEIHCsdMa8MU6GBBM9DmFg3qUKjaQSZ04Vkz8Gyo0ARQlJaIiLohNIEg - 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 - - - \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs new file mode 100644 index 0000000000..3b45c33571 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs @@ -0,0 +1,116 @@ +namespace Rdmp.UI.SimpleDialogs +{ + partial class ViewRedactedCHIsInCatalogueDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + btnSearch = new System.Windows.Forms.Button(); + dtResults = new System.Windows.Forms.DataGridView(); + lblLoading = new System.Windows.Forms.Label(); + btmRevertAll = new System.Windows.Forms.Button(); + btnConfirmAll = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)dtResults).BeginInit(); + SuspendLayout(); + // + // btnSearch + // + btnSearch.Location = new System.Drawing.Point(12, 12); + btnSearch.Name = "btnSearch"; + btnSearch.Size = new System.Drawing.Size(75, 23); + btnSearch.TabIndex = 0; + btnSearch.Text = "Search"; + btnSearch.UseVisualStyleBackColor = true; + btnSearch.Click += searchButtonClick; + // + // dtResults + // + dtResults.AllowUserToAddRows = false; + dtResults.AllowUserToDeleteRows = false; + dtResults.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + dtResults.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dtResults.Location = new System.Drawing.Point(12, 54); + dtResults.Name = "dtResults"; + dtResults.ReadOnly = true; + dtResults.RowTemplate.Height = 25; + dtResults.Size = new System.Drawing.Size(776, 384); + dtResults.TabIndex = 1; + // + // lblLoading + // + lblLoading.AutoSize = true; + lblLoading.Location = new System.Drawing.Point(405, 182); + lblLoading.Name = "lblLoading"; + lblLoading.Size = new System.Drawing.Size(59, 15); + lblLoading.TabIndex = 2; + lblLoading.Text = "Loading..."; + // + // btmRevertAll + // + btmRevertAll.Location = new System.Drawing.Point(93, 12); + btmRevertAll.Name = "btmRevertAll"; + btmRevertAll.Size = new System.Drawing.Size(75, 23); + btmRevertAll.TabIndex = 3; + btmRevertAll.Text = "Revert All"; + btmRevertAll.UseVisualStyleBackColor = true; + btmRevertAll.Click += RevertAll; + // + // btnConfirmAll + // + btnConfirmAll.Location = new System.Drawing.Point(174, 12); + btnConfirmAll.Name = "btnConfirmAll"; + btnConfirmAll.Size = new System.Drawing.Size(91, 23); + btnConfirmAll.TabIndex = 4; + btnConfirmAll.Text = "Confirm All"; + btnConfirmAll.UseVisualStyleBackColor = true; + btnConfirmAll.Click += ConfirmAll; + // + // ViewRedactedCHIsInCatalogueDialog + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(800, 450); + Controls.Add(btnConfirmAll); + Controls.Add(btmRevertAll); + Controls.Add(lblLoading); + Controls.Add(dtResults); + Controls.Add(btnSearch); + Name = "ViewRedactedCHIsInCatalogueDialog"; + Text = "Redacted CHIs in Catalogue"; + ((System.ComponentModel.ISupportInitialize)dtResults).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Button btnSearch; + private System.Windows.Forms.DataGridView dtResults; + private System.Windows.Forms.Label lblLoading; + private System.Windows.Forms.Button btmRevertAll; + private System.Windows.Forms.Button btnConfirmAll; + } +} \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs new file mode 100644 index 0000000000..56d00a5e23 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -0,0 +1,126 @@ +using HICPlugin.Curation.Data; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.Curation.Data; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Rdmp.UI.SimpleDialogs +{ + public partial class ViewRedactedCHIsInCatalogueDialog : Form + { + private bool _isLoading = true; + private IBasicActivateItems _activator; + private ICatalogue _catalogue; + + + public ViewRedactedCHIsInCatalogueDialog(IBasicActivateItems activator, ICatalogue catalogue) + { + InitializeComponent(); + _activator = activator; + _catalogue = catalogue; + lblLoading.Visible = _isLoading; + dtResults.Visible = !_isLoading; + FindChis(); + } + + private void RevertButtonClick(int itemIndex) + { + //todo ExecuteCommandRevertRedactedCHI + } + + private void ConfirmButtonClick(int itemIndex) + { + //todo ExecuteCommandConfirmRedactedCHI + } + + private void handleClick(object sender, DataGridViewCellEventArgs e) + { + if (e.ColumnIndex == dtResults.Columns["Revert"].Index) + { + RevertButtonClick(e.RowIndex); + } + if (e.ColumnIndex == dtResults.Columns["Confirm"].Index) + { + ConfirmButtonClick(e.RowIndex); + } + + } + + private void RevertAll(object sender, EventArgs e) { + if (_activator.YesNo("Do you want to revert all these redactions?","Revert All")) + { + //todo ExecuteCommandRevertCHIRedactionsForCatalogue + } + } + private void ConfirmAll(object sender, EventArgs e) { + if (_activator.YesNo("Do you want to confirm all these redactions?", "Confirm All")){ + //todo ExecuteCommandConfirmtCHIRedactionsForCatalogue + } + } + + private string locationToColumn(string location) + { + var lastIdx = location.LastIndexOf('.'); + return location[(lastIdx+1)..]; + } + + private void FindChis() + { + _isLoading = true; + lblLoading.Visible = _isLoading; + dtResults.Visible = !_isLoading; + List columns = _catalogue.CatalogueItems.Select(ci => ci.ColumnInfo).Select(ci => ci.Name).ToList(); + List redactedChis = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(rc => columns.Contains(rc.CHILocation)).ToList();// + + + + var dt = new DataTable(); + dt.Columns.Add(new DataColumn("Potental CHI", typeof(string))); + dt.Columns.Add(new DataColumn("Context", typeof(string))); + dt.Columns.Add(new DataColumn("Column", typeof(string))); + foreach (var rc in redactedChis) + { + dt.Rows.Add(new object[] {rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation) }); + } + dtResults.DataSource = dt; + DataGridViewButtonColumn revertColumn = new DataGridViewButtonColumn(); + revertColumn.Text = "Revert"; + revertColumn.Name = "Revert"; + revertColumn.UseColumnTextForButtonValue = true; + + DataGridViewButtonColumn confirmColumn = new DataGridViewButtonColumn(); + confirmColumn.Text = "Confirm"; + confirmColumn.Name = "Confirm"; + confirmColumn.UseColumnTextForButtonValue = true; + dtResults.CellClick += handleClick; + if (dtResults.Columns["Revert"] == null) + { + dtResults.Columns.Insert(3, revertColumn); + } + if (dtResults.Columns["Confirm"] == null) + { + dtResults.Columns.Insert(4, confirmColumn); + } + _isLoading = false; + lblLoading.Visible = _isLoading; + dtResults.Visible = !_isLoading; + } + + + private void searchButtonClick(object sender, EventArgs e) + { + _isLoading = true; + lblLoading.Visible = _isLoading; + dtResults.Visible = !_isLoading; + + FindChis(); + } + } +} diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.resx b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.resx new file mode 100644 index 0000000000..af32865ec1 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file From 50fd26f4f440a8c2f2d8c4bbe9fef73041e0bdc7 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 28 Nov 2023 11:58:17 +0000 Subject: [PATCH 06/64] make start on ui --- .../AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs | 7 +++---- .../ExecuteCommandViewRedactedCHIsInCatalogue.cs | 2 +- Rdmp.UI/Menus/CatalogueMenu.cs | 2 +- Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs | 2 ++ 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 530d0e7dda..3ffcc41da5 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -19,13 +19,12 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandIdentifyCHIInCatalogue : BasicCommandExecution, IAtomicCommand { - private Catalogue _catalouge; + private ICatalogue _catalouge; private IBasicActivateItems _activator; private bool _bailOutEarly; private readonly Dictionary> _allowLists = new(); - - public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] Catalogue catalogue, bool bailOutEarly = false, string allowListLocation = null) : base(activator) + public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, bool bailOutEarly = false, string allowListLocation = null) : base(activator) { _catalouge = catalogue; _activator = activator; @@ -63,7 +62,7 @@ private void handleFoundCHI(string foundChi,string contextValue, string columnNa var shrunkContext = WrapCHIInContext(foundChi,contextValue); foundChis.Rows.Add(foundChi, shrunkContext, columnName); } - private DataTable foundChis = new(); + public DataTable foundChis = new(); public override void Execute() { diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs index 04ff30921a..19f0b52bea 100644 --- a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs @@ -29,6 +29,6 @@ public override void Execute() { base.Execute(); var dialog = new ViewRedactedCHIsInCatalogueDialog(_activator,_catalogue); - dialog.Show(); + dialog.Show(); } } diff --git a/Rdmp.UI/Menus/CatalogueMenu.cs b/Rdmp.UI/Menus/CatalogueMenu.cs index b9e3011070..4c01d43206 100644 --- a/Rdmp.UI/Menus/CatalogueMenu.cs +++ b/Rdmp.UI/Menus/CatalogueMenu.cs @@ -24,7 +24,7 @@ internal class CatalogueMenu : RDMPContextMenuStrip public CatalogueMenu(RDMPContextMenuStripArgs args, Catalogue catalogue) : base(args, catalogue) { var isApiCall = catalogue.IsApiCall(); - + Add(new ExecuteCommandRedactCHIsInCatalogue(_activator, catalogue)); Add(new ExecuteCommandViewRedactedCHIsInCatalogue(_activator,catalogue)); Add(new ExecuteCommandGenerateMetadataReport(_activator, catalogue) diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 56d00a5e23..07ef37a890 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -53,6 +53,8 @@ private void handleClick(object sender, DataGridViewCellEventArgs e) } + //TODO allowlist + private void RevertAll(object sender, EventArgs e) { if (_activator.YesNo("Do you want to revert all these redactions?","Revert All")) { From f23507055c066f236926bf597aec5a639e91fb19 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 28 Nov 2023 15:30:34 +0000 Subject: [PATCH 07/64] basic ui --- .../ExecuteCommandConfirmRedactedCHI.cs | 19 + .../ExecuteCommandIdentifyCHIInCatalogue.cs | 2 +- .../ExecuteCommandRedactCHIsFromCatalogue.cs | 9 +- .../ExecuteCommandRevertRedactedCHI.cs | 51 + .../ExecuteCommandRedactCHIsInCatalogue.cs | 28 + .../RedactChisInCatalogueDialog.Designer.cs | 119 +++ .../RedactChisInCatalogueDialog.cs | 103 ++ .../RedactChisInCatalogueDialog.resx | 120 +++ Rdmp.UI/SimpleDialogs/SelectDialog`1.resx | 982 ++++++++++++++++++ .../ViewRedactedCHIsInCatalogueDialog.cs | 64 +- 10 files changed, 1480 insertions(+), 17 deletions(-) create mode 100644 Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs create mode 100644 Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs create mode 100644 Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs create mode 100644 Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs create mode 100644 Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs create mode 100644 Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.resx create mode 100644 Rdmp.UI/SimpleDialogs/SelectDialog`1.resx diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs new file mode 100644 index 0000000000..dec70626c3 --- /dev/null +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs @@ -0,0 +1,19 @@ +using HICPlugin.Curation.Data; +using Rdmp.Core.Curation.Data; + +namespace Rdmp.Core.CommandExecution.AtomicCommands; + +public class ExecuteCommandConfirmRedactedCHI : BasicCommandExecution, IAtomicCommand +{ + RedactedCHI _redactedCHI; + public ExecuteCommandConfirmRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("redactionto confirm")]RedactedCHI redaction): base(activator) + { + _redactedCHI = redaction; + } + + public override void Execute() + { + base.Execute(); + _redactedCHI.DeleteInDatabase(); + } +} diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 3ffcc41da5..cabe83895a 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -29,7 +29,7 @@ public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [Dema _catalouge = catalogue; _activator = activator; _bailOutEarly = bailOutEarly; - if(allowListLocation != null) + if(!string.IsNullOrWhiteSpace(allowListLocation)) { var allowListFileContent = File.ReadAllText(allowListLocation); var deserializer = new DeserializerBuilder().Build(); diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index 514c7fda9a..b9233f8cb4 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -22,16 +22,16 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandRedactCHIsFromCatalogue : BasicCommandExecution, IAtomicCommand { - private Catalogue _catalouge; + private ICatalogue _catalouge; private IBasicActivateItems _activator; private readonly Dictionary> _allowLists = new(); + public int redactionCount = 0; - - public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] Catalogue catalogue, string allowListLocation = null) : base(activator) + public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, string allowListLocation = null) : base(activator) { _catalouge = catalogue; _activator = activator; - if (allowListLocation != null) + if (!string.IsNullOrWhiteSpace(allowListLocation)) { var allowListFileContent = File.ReadAllText(allowListLocation); var deserializer = new DeserializerBuilder().Build(); @@ -45,6 +45,7 @@ public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [Dem private void handleFoundCHI(string foundChi, string table, string column, string columnValue) { Console.WriteLine("Found CHI!"); + redactionCount++; var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi,columnValue,20),$"{table}.{column}"); rc.SaveToDatabase(); var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs new file mode 100644 index 0000000000..795ebf9298 --- /dev/null +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -0,0 +1,51 @@ +using HICPlugin.Curation.Data; +using Microsoft.Data.SqlClient; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.ReusableLibraryCode.DataAccess; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.Core.CommandExecution.AtomicCommands; + +public class ExecuteCommandRevertRedactedCHI : BasicCommandExecution, IAtomicCommand +{ + RedactedCHI _redactedCHI; + IBasicActivateItems _activator; + + public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHIto Revert")] RedactedCHI redaction) : base(activator) + { + _redactedCHI = redaction; + _activator = activator; + } + + public override void Execute() + { + base.Execute(); + var splitidx = _redactedCHI.CHILocation.LastIndexOf('.'); + var table = _redactedCHI.CHILocation[..splitidx]; + var column = _redactedCHI.CHILocation[(splitidx + 1)..]; + var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == _redactedCHI.CHILocation).First(); + var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; + var findSlq = $"select {column} from {table} where {column} like '%REDACTED_CHI_{_redactedCHI.ID}%';"; + var existingResultsDT = new DataTable(); + using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + { + con.Open(); + var da = new SqlDataAdapter(new SqlCommand(findSlq, con)); + da.Fill(existingResultsDT); + if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) + { + var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); + var newContext = currentContext.Replace($"REDACTED_CHI_{_redactedCHI.ID}", _redactedCHI.PotentialCHI); + var updateSQL = $"update {table} set {column}='{newContext}' where {column} = '{currentContext}'"; + var updateCmd = new SqlCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + _redactedCHI.DeleteInDatabase(); + } + } +} diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs new file mode 100644 index 0000000000..f973e5cf7b --- /dev/null +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs @@ -0,0 +1,28 @@ +using Rdmp.Core.Curation.Data; +using Rdmp.UI.ItemActivation; +using Rdmp.UI.SimpleDialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.UI.CommandExecution.AtomicCommands; + +public class ExecuteCommandRedactCHIsInCatalogue: BasicUICommandExecution +{ + private ICatalogue _catalogue; + private IActivateItems _activator; + public ExecuteCommandRedactCHIsInCatalogue(IActivateItems activator, ICatalogue catalogue) : base(activator) + { + _catalogue = catalogue; + _activator = activator; + } + + public override void Execute() + { + base.Execute(); + var dialog = new RedactChisInCatalogueDialog(_activator, _catalogue); + dialog.Show(); + } +} diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs new file mode 100644 index 0000000000..ba9bbfa153 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs @@ -0,0 +1,119 @@ +namespace Rdmp.UI.SimpleDialogs +{ + partial class RedactChisInCatalogueDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + btnExecute = new System.Windows.Forms.Button(); + dgResults = new System.Windows.Forms.DataGridView(); + cbDoRedaction = new System.Windows.Forms.CheckBox(); + label1 = new System.Windows.Forms.Label(); + tbAllowList = new System.Windows.Forms.TextBox(); + lbResults = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)dgResults).BeginInit(); + SuspendLayout(); + // + // btnExecute + // + btnExecute.Location = new System.Drawing.Point(12, 9); + btnExecute.Name = "btnExecute"; + btnExecute.Size = new System.Drawing.Size(75, 23); + btnExecute.TabIndex = 1; + btnExecute.Text = "Find CHIs"; + btnExecute.UseVisualStyleBackColor = true; + btnExecute.Click += FindCHIs; + // + // dgResults + // + dgResults.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dgResults.Location = new System.Drawing.Point(12, 84); + dgResults.Name = "dgResults"; + dgResults.RowTemplate.Height = 25; + dgResults.Size = new System.Drawing.Size(759, 376); + dgResults.TabIndex = 2; + // + // cbDoRedaction + // + cbDoRedaction.AutoSize = true; + cbDoRedaction.Location = new System.Drawing.Point(106, 13); + cbDoRedaction.Name = "cbDoRedaction"; + cbDoRedaction.Size = new System.Drawing.Size(97, 19); + cbDoRedaction.TabIndex = 3; + cbDoRedaction.Text = "Do Redaction"; + cbDoRedaction.UseVisualStyleBackColor = true; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(200, 13); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(110, 15); + label1.TabIndex = 4; + label1.Text = "Allow List Location:"; + // + // tbAllowList + // + tbAllowList.Location = new System.Drawing.Point(316, 9); + tbAllowList.Name = "tbAllowList"; + tbAllowList.Size = new System.Drawing.Size(260, 23); + tbAllowList.TabIndex = 5; + // + // lbResults + // + lbResults.AutoSize = true; + lbResults.Location = new System.Drawing.Point(325, 52); + lbResults.Name = "lbResults"; + lbResults.Size = new System.Drawing.Size(0, 15); + lbResults.TabIndex = 6; + // + // RedactChisInCatalogueDialog + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(800, 450); + Controls.Add(lbResults); + Controls.Add(tbAllowList); + Controls.Add(label1); + Controls.Add(cbDoRedaction); + Controls.Add(dgResults); + Controls.Add(btnExecute); + Name = "RedactChisInCatalogueDialog"; + Text = "RedactChisInCatalogueDialog"; + ((System.ComponentModel.ISupportInitialize)dgResults).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private System.Windows.Forms.Button btnExecute; + private System.Windows.Forms.DataGridView dgResults; + private System.Windows.Forms.CheckBox cbDoRedaction; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox tbAllowList; + private System.Windows.Forms.Label lbResults; + } +} \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs new file mode 100644 index 0000000000..f5bc5500a1 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -0,0 +1,103 @@ +using HICPlugin.Curation.Data; +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.Curation.Data; +using Rdmp.UI.ItemActivation; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using YamlDotNet.Serialization; + +namespace Rdmp.UI.SimpleDialogs +{ + public partial class RedactChisInCatalogueDialog : Form + { + + private IActivateItems _activator; + private ICatalogue _catalogue; + private DataTable _results; + + public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogue) + { + InitializeComponent(); + _activator = activator; + _catalogue = catalogue; + dgResults.Visible = false; + lbResults.Visible = false; + + } + + private void Redact(int rowIndex) + { + var result = _results.Rows[rowIndex]; + var foundChi = result.ItemArray[0].ToString(); + var columnValue = result.ItemArray[1].ToString(); + var column = result.ItemArray[2].ToString(); + var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); + var name = catalogueItem.ColumnInfo.Name; + var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, columnValue, name); + rc.SaveToDatabase(); + result.Delete(); + _results.AcceptChanges(); + dgResults.DataSource = _results; + } + + private void handleClick(object sender, DataGridViewCellEventArgs e) + { + if (e.ColumnIndex == dgResults.Columns["Redact"].Index) + { + Redact(e.RowIndex); + } + } + + + private void ShowResults() + { + dgResults.DataSource = _results; + DataGridViewButtonColumn confirmColumn = new DataGridViewButtonColumn(); + confirmColumn.Text = "Redact"; + confirmColumn.Name = "Redact"; + confirmColumn.UseColumnTextForButtonValue = true; + dgResults.CellClick += handleClick; + if (dgResults.Columns["Redact"] == null) + { + dgResults.Columns.Insert(3, confirmColumn); + } + dgResults.Visible = true; + lbResults.Visible = false; + } + + private void FindCHIs(object sender, EventArgs e) + { + dgResults.Visible = false; + lbResults.Visible = false; + if (this.cbDoRedaction.Checked && _activator.YesNo("Are you sure you want to blindly redact?", "Redact Catalogue")) + { + var cmd = new ExecuteCommandRedactCHIsFromCatalogue(_activator, _catalogue, tbAllowList.Text is not null ? tbAllowList.Text : ""); + cmd.Execute(); + lbResults.Text = $"Redacted {cmd.redactionCount} CHIs in the catalogue"; + dgResults.Visible = false; + lbResults.Visible = true; + } + else + { + var cmd = new ExecuteCommandIdentifyCHIInCatalogue(_activator, _catalogue, false, tbAllowList.Text is not null ? tbAllowList.Text : ""); + cmd.Execute(); + _results = cmd.foundChis; + ShowResults(); + } + + } + + } + + + +} diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.resx b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.resx new file mode 100644 index 0000000000..af32865ec1 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx new file mode 100644 index 0000000000..68cc010e31 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx @@ -0,0 +1,982 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + R0lGODlhZABkAPcAAPhw5P7+/v78/vLc8P5K5P4g3P4K2v6a8Pym7vxE4Pw24PTE7vy68vyi8Pb29v5q + 6P484P4W2v6q8vx86PqW6vz6/Pj4+Ppq5PpM4PiO6Pz6+viC5vh45vi68PiK6Pz4/Pzr+fj2+PbE8Paa + 6PrO8vTS8PLy8vTM7vj0+Pjt9vTw8vTs9PLs8v5a5v4w4Pxg5vqM6v5x6P464v4a3PyS7Pqi7vpu5vpm + 5Piy7viA6Pau7Pbn9Pai6PTW7vyF7Pqd7PqY7Pic7PqE6PyE6vx+6vx86viW6vas6vxC4PTo8vxU5PzU + 9v4Y2vTm8v4c3P5Y5P5M5Ppu5Pps5Ppc5Pjc8/bW8PxI4vxC4vTi8P4q3vxG4Pw+4P5A4PTg8v4e3P4o + 3v4k3v4i3v4e3v4m3P4W3PTc8vjI8PbS8PzH9PbM8Pqz8PzC8vjC8P5C4vi88P5I5Py+8vy88vut8P6q + 8Pyo8P6i8Pym8Pyk8PqQ6v566vqK6vp+6PqO6vpw5viK5vpg5PqI6v5/6/6K7vye7vyK7PyI6vyA6vyC + 6vx16Pxc5PxM4vxK4PxI4Pi47va27Pig6vie6v6Q7fyM6vxv5/6c7vyO6vyO7PyQ7Pxr5/5i5vqm7vqg + 7P5W5Pxi5vyg7vqm7PxY5Pqo7v5U5Pqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5Pqy7vxK4va87P689PbA + 7vz8/P7S9/464P4t3/444P424P404P4y4PxS4v76/v7n+vro9/ya7vxe5PyW7Pxe5vxg5Pp05vxk5Prf + 9vy48vyS7vp25vzb9/qC6P7E9f7g+P76/Pp45vbW8vq68Pp66Pz4+vrA8vr4+fz0/PrX9ffw9/Xx9Pbu + 9PzY9/ji9PbS8vjT8vbQ8vzN9fu28fzC8/jE8PjA8Py99Puw8Pyq8P6o8P649P7A9P78/P7a+P7w/Pry + +Prj9/zh+P7L9v7j+vbf8vrI8vy28vyv8vzy+vrO9P546vqk7vyJ6/jK8P6I7P4w3vrs+Pz2/Pz2+gAA + ACH5BAAFAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAI/gADCBxIsKDBgwgTCtTgwIRDBxUU + SpxIsaLFiwJR6GnzxkO1iBhDihwZkpCMkzL4aCDJsqVLgRXaoJRBYOXLmzgphpgpA4LNnECDDnTA06fQ + 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 + PI9WCGup3FW1DOO35OjwhWKtEKq2FlwSrACCRQvsTPM6IfRILir6nBOzilYclCr1aSHWjeS+GknTKIZo + i+FeYxN6fpJ2LCko3SFX/BY1E5H2AdJ2jOS+dkWhFm0d0qbutAkAua0Fcock8Nv7dOrYEtFFclXboQr8 + lhTMU7euC5KM5K5IA9o+q4LrguaJJFvcK4LFh88a4n/r+CIlhuuJQB/ejcS+G8AVp0g6XPFfCXZV4VKp + tQ7VzWxEO9q7Mrf5RIaLWuPq/CimRS3Bfx4UaCBWsTUn+oykYQG25vwlAQEAIfkEAAUAAAAsAAAAAGQA + ZACH9nzk/v7+/vz+8t7w/kTi/iTe/gTa/pTu/Jzs/Eri/Bzc8szs/Jru+Pj4/mjm/jTg/gba/qTw/I7q + +oTo+vr6+lLi+j7e+qTu+Ijm+vj6+vj4+Gzi+GLi+LTu/Pb79ojm9sLu8vLy9pLo/hTc+fP49vL19PL0 + 9OLy9NTw9Mbu9Lbq9Kzq9KLo/Mj08ujw8uLw9erz9Oby9Njw9M7u/lTm/jLg/HLo/ELg/nTq/jrh/hLa + /Jbs+pLs+lji+Ibo+tn1+Jjq+HDk9rDs/hnc9Lzs/JTs/JLs/JDs/I7s/Izs/mTo+pLq+orq+JLq9p7q + /Fzk+obo+JDo+Ijo9try/iLc/mrm/kbi/FLi+OD09OTy/FDi/Ezi+lTi/ibe/DLc+kjg+kTg/CTc/CDc + /B7c+Mvy/hDa/gza/gra/gja+rfw9Nbw9tLy9s7w+L7w/kLi/LXx+Lzw+Lru+Lbu+rDw/Krw+qju/KXv + +qbu/KDu/qLw/qDw/pnv/J7u/Jzu/n3q/mzo/nbo/IPq/Gnm/Fjk/E7i+mLk+H3m/oTs+nvn/Dze/Cjc + /Hjo+pjs+pTq+pDq/ovs/pLu/HTo+Jrs9rLs9MTs+JTq9qbq+prq/GTm+pzs+Jzq/lvm/GDk/lTk+p7s + +KLq+qDs+KLs+KTq/qrx/C7c+Kzs/rT0/FTi/lLk/lDk/kzk9r7s/Eji/rjz/Ebi/Ebg/Pz8/Dzg/vr+ + /sn2/D7g/DTe/jDe/Frk/tX4/Nr3/izg/ize/ire/ije+mTk/uL6+mjk8uzy/Gzm+r3x+mrk/Gzo+nDm + /G7m/Of4/HTm+snz9N7w+Ov2/vr8+nLk+nLm/MPz+pbs+eT1/iDc+NTz9N7y/Pr7/Pj8+vb69vX29PT0 + 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 + +1Hq2vWuediQ3SxckGaQI7StcF6CgDAEfiaixAFoQTrFm5FWzPZmFZauwcQa2ndkhBnOOAM4gXARfEDi + EaNARg5la9lzGAwWLDYIPnr7W7GsoQJmUAAGjDsSAYxitu4VCP6WhXyQ6oYWH40NwCyH+BpczJYcYFoz + bvXH3sCKbiQ4FYkH1tHf8ao5ywa5xoAD2wop8jIA+JhtaRCiZ87GEh4ZfnQ9TrzVCKjYIJXebCxhwVtG + E3aXPrZsfCiN6IOANrT+CHRV4jFbdnwq1BH46FHbm0ssz3ayoG71QTzQ23bIWig6tSw0RIbrEgM2tH8+ + 4KAtuw5DF6TZ6Fv0WBu9wnbMdrTGETZC4tFbbv6oHrMdRfCwTZHecflH7JjtNyzCboq4ObCeRRAszOu+ + elNk19uGkTUIvVX/0kXcCSE2o2EECwlvtUEHZ/NFMEzWH1ljG3wFN70RrhB4tKMd0WYhoCs5bpxHYwTA + JudLeo+d8onclrO6bTlSNMvZfMv8b3vta5xvbpEmLWXnQQkIACH5BAAFAAAALAAAAABkAGQAh/aM5v7+ + /v78/vLe8P5M5P4e3P4M2v6c8Pyk7vwk3PSw6vy48vyi8Pb09v5s6P4+4P4O2v6s8vx66Pz6/Pr4+vp6 + 5vpY4vpE4Pqu8PqY6vz6+vj4+Phy5Pho4viY6vz4/Pz4+vj2+Pj0+Pj09vaW6PbE8Pam6Pzc9/rr9/b2 + 9vTW8PLq8vTG7vS47Pjk9fTy9PTY8PLk8P5c5v404Pxa5P587P484v4c3PyK7PqG6vpw5viC5vqh7fig + 7PTp8/TQ7vTI7vS+7v4a3PyE6vqc7PqW7P5y6fx86Pic6via6vaq6vp+6Pp66P5O5Pq88Pp85vpm5PxK + 4vpY5Pbd8vw83vjS8v4s4PpO4P4u3v4q3v4o3vww3vwm3P4g3P4U3P4S3P4Q3PzN9P4Y2v4W2v4U2v4Q + 2vbU8PbQ8vq48PzE9PzB8v5D4vy88v5K5Py08Pq28Pqy8Pys8P6q8vyo8P6k8Pym8P6e8PqT6vp25viO + 6PqM6PqA5vpu5vpe5P6H7P6A6vqM6vqC6PyS7PyI7Px+6vyA6v6M7vxr5vxY4vxC4Pw23vws3v6T7v6a + 7v5j5/5a5vyU7PyW7Pqk7Pim7PTO7vqe7PqY7Pii6vyY7Pya7Pxh5vya7vyc7vxf5Pyg7vqq7vxc5P5Y + 5vqk7vqo7v5W5Pio7P5T5PxS4/i47viw7vqs7v6y8vqu7vxO4v689Pa07PbC7vi+7vw63v7A9Pz8/P7c + +P464P4w4P444P424PxG4vxM4PrS9Pp45vxY5P76/v76/Pzx+vTa8P7I9vTe8PyQ7PjA8Prd9vLy8vyN + 7PrF8vzn+fby9P5+6vja9P7s+v586vx05/7n+v7Q9vr6+vzi+Pr2+vfs9vT09PTs9Pq/8fTk8vjW8/zV + 9vbW8PzJ9PzA9Py+8vu18vq08Pyu8Pyq8P6o8v6g8P669P7D9v78/P7g+frX9fzz+/7M9vjG8Prj9vrM + 8/zs+vfx9vjf9P7q+v7U+Pq/8vyv8vjK8vr0+Pbt9P566vzQ9f5K4gAAAAj+AAMIHEiwoMGDCBMKpLah + wYsGG2gpnEixosWLGAVag0QqVJEGEjOKHElSJCQCKAlYmlCypcuXAieESkngEUuYOHNWpECTQBNqOoMK + LRii58+hSBHSokDt5sUGRimIpAU0KcYJCwj9WeC0IlSaTaRenDDHkaNPG6xaJCejrQwMGL+mDHuRViYb + eG3wqKo2oQAHbmU4WvY06kV6a/LaaJK2r19HgWUICklRLkq6FTdIUGxjjVjHCItFltHN4gvDFUtwtiGB + MmiDLiAHhtY14WmwnxU2kMF5zbHXCWkhGM2m4u25uZWWWg3JNfCC1wAHdqCP4vHLyQ/6IMC5SfXnSp3+ + jEbg3OB1n9kLUgO0Gg543RIiO4I38TzmhN4ecHZU+33BKqPh0B9B9qU30AY6rBaPfwpRIFpkYShUoEKx + rEbIgAwO5IImkRlhYAATIsRMKL25kKFCZI3mhm2oGTQBD6sRceJE80jnlgPyIBSiQdvowpkp382YkDGj + cVIeiC0SREEOqzkB2lITHDnRBn+M9pt5SQ5UhX6KHYKhQrQI8OVBE4jDjxGb0JcRN6MN0Z9l6BkUwiGc + PcBLRtRw48AtNhQxJkHhOCCoA0bEEQxG1BQxmjIGwXnfQK+sloOUB9Hiww63WKGpFZJURAsOgw7Kjxp/ + EuSDbG4ZYU1Bjia3jyn+nLWBwlOX2LDppjR8SJAhoYbqBzuUEkQLBqORw2qS1GSwWqeZwXLKrbfS0NhE + s/Ta6wGzVmaEfNkK5ChfAUyRmGI0rDoRNcLwAS20k4Cr0AJGWDuoEW6Yq9A7o0VCWVFgOUXNE6tpM5Gl + esyw7qYzZKArbJzIO2gN34xJgR+jsTMQT2BRls9qOrjLqhJrHLxpNGriyY0fDguazAmUOjPahQIJYApN + j4Q0gSO9OZPQBkAgIrKmpORTqkIhqMFPyg4w0G1BE1QyWoQCMUITJiGFs1oxR05ADB4/W/HAJwtfNE8c + 8TpsxALpRReZsQJ9EElHmNh7AGcELD3QPncYLPL+LTjY/dIELkSC9B9h1EbLApF9M9BSDW3wmSucwUXU + K210fYgzwbZEQRVDIC1INUxT3BYOAlA0gRF5eUjQBj9o0nUTxgztUghOlC2vEXMcOpAA6ciRjuxYRcLV + 4tsskanItvAQwmu0XIMA0vyEI3twSlZiy89YBOK3Y9TQU4zn00/0ges/a8JN5mpRkE8zKSsOEzk/64JG + 2KCJII7toa6CEw4H55KBCEISlgs8YS33vQQf6+pFyQJIEGo4A1SCYkTpYPIBR+AqHuHzzwSmsYBpTBAn + WCmCNjLIwBKa8IQFocUEVsjCFrrwhTBcIfpcMoEUvOCGOMyhDneYwxTwhRb+1nCDH2pAxCIa8YhIRKIf + VkFCi3xAAlkogBSnSMUqWtGKWpBAdSbghiR68YtJ1N9QJHDFMprxihJY4RDByMYv+mGGGZlAFM9IRzNm + gQITWGMb92hEPwyFGnOsoyCpmIWIdJGPiKyBGIMygSMM8pFSrIBUuKjHRHpxiR8MigigCEk6ZqECzHAK + VTZAylKa8pSoLCVT+jIBCqTylbA0JR5RSMta2tJFtUhDN5qYkAnEYQhuoN+MqsEAQxhTDplsiTWiYIBm + UiEI0zphMFZhzGoawh44QUAzt2mATsBAmK+hQBoYYc1qpgMnveDmNsewgxXAUS206EYjymlNbMJEEur+ + 5GYWTNCAEy2jDvS0ZgRyYo0m5JObF2BBNF/TjgUEtJqMCEMyZ5eBAhy0mRDowAA8lpQJfIOcDzUEG4IU + AAHUYhi1mChCBCCNWkhDgDoAw0UNIAQAZOOdJKHFCQ4QUkNEAAWu4eIBhroAi0zDFUidBtOqsIuZGmAL + CkhBUpYRgZ7agR39scdQt7qOxU2gKa6RBlLH+tKCUCAVWHDqFX6w0Je0gw09ZcQ30vMBOmx1qEqNyTRW + sQoPDqQWY0VqOj5wEBHoQQwz/QIHYsBLpoUBpA9dwPYEkoa7DrWrAplGBDYbgbwGoB6BRWotlAIPKEBg + pgUgwQtccoJz9HQO8Pj+kjzsYNlFBmAVnI2AbYcRWlfo7iDUyEcinKqIFrS1IvLAR08b0Q2OLm4Vlj0A + YQaS280S5Bm9nUawKDAJLTjVAiqY3gTGGVJGpGF5E9mpZWfhmupGwDX26O10FSKCPXhhpl7IwdBo8Y2e + ugGoppODZc9B0gC4tysfmEVoh1EqWhyjE07FREUmQIeHMiC2Ro0u1BZ34IIANrSYpQg1YiGLi66hwRUu + ZyP6Ac5gnMOyckgOLTpMEN6FdhaEtQgFiNCFfCbCuQUJgzUZAY4CU2QW0T2BQWZc3f6INbSlwcg18PAF + bsrIIsM4ACNWAWCMSCO64ugPk3M7IHf09rcWmYD+GWgwhgTkAZwtEQB072qH7Y2ZsxwNRm/dgdOFbCAF + cG6Jeu+ahiPdebPOjW9o55uRPpfkA+awLB2MLJBDR8C5H0hHaH9nS3dEtx/BcS+Q19Hb0dLSxZaNwIcs + DWQB8Daws2gHLV0RXdCFurpA/mx2UbiM6A7v1rnNdQCOGtpnmHACEbCsHXIEJlFPpB29HYZKGdSN6A4j + c6ymyIcDa2sh1VXS9gI2nk2n6cByWkhZtmyUB+ZsilSj1EKarWXxsd92T8TVvZX1idAR3QU2G9cWwW5o + 7cmgCdD2rmyAo6Wnp+ixzuJEFL7rOdDM7iZfJMHmPhEtpnFXz3qKxhYh9Vg+Q1zwbvCV4BZZeEaq4Q53 + dPuW4t5sY2FOEZDTPCjudfTNK4Jbztp25znRLGc9DnQQ7rWvMy86majB9KTDJCAAIfkEAAUAAAAsAAAA + AGQAZACH9obm/v7+/vz+8uDw/kbk/hbc/gba/pbw/Jbu/Bra8srs/KDw+Pj4/mbo/ibe/gja/rb0/Kru + /HLo+vr6+mTk+jze+q7w+pLq+vj6+Hzk+Gzk+GLi+Fjg+MDw9pro/Pf79szw8vLy9pbo/M/1+fb49/P2 + 9vL09O709OTy9Nrw9NLw9Kro/hTa+uj39PL08ury8tbu+Nz09vD29PD09Ory9ODw8uzy/lbm/hjc/Kju + /Kbu/KTu/KLu/KDu/J7u/Jzu/Jru/E7i/nbq/i3e/hDa/Ijs+oDo+lbi+prs+Iro+G7k+Jrq9uXz9MTu + 9Kjq8tzu+pDs+KDs9Lrs/m3o/IDq/Hbo+prq+JTq+nTo+Ijo+JLo+nDm+Ibm9Oby/lTk/kjk+mzk+s/y + 9tjy/Dze+lLi+kbg/DDe/Cje/CLc9Nzw/g7a/gza/gra+NHy/jbg9tLy9tLw/Lzy+rzw+Mjw/j3h+MLw + /kzi/LXw+rrw+rbw/qby+rLw/LLx/K/w/Kvw/qbw/Kbw/p7w/n7q/nLq+ojp+mDk+HLk+nzm+nbm+lTi + +krg+pzs/ILq/Hjo/obs/Hrq/H7o/IDo+nro/Ibq/o/t/Ijq/pzu/Gbl/ETh/Dbe/Cre+LLu9sLu9Mju + 9LLq+Kzs+Kbs9Lzs+Jbq9qDo/l7m/lDk/Jzs+p7s+qDs/Fvk+lzk+qTu+qbs/Fbk+qbu+qju/FLj+q7u + /k7k/kzk/krk+Lju/rrz/Cze+Lru/vz8/EHf/vr+/vr8/sX1/D7g9sTu/t/5/DPe9NDu+mTi+JDo/Nz3 + /Eri/Jbs/iTe/Ezg/iLc+K3u/h7c/I/s+J7q+Hbk/Of4/hze/hzc/vj8/hre/hrc/Izs/vb8+pTs/tf4 + +t/2+Or2+nzo+Kjs+n7o/kri/pru/PD6/jTg/Mf0+sby/kji/kbi/q/y/pju/tL29q7s/Pv8/Pj8/NX2 + +vb69/P39fP1+vH4+OH09Oz09ur0+tf09uLy+Nbz/MDy+r7w+Mry+MTw/Lny+rry+rjwAAAACP4AAwgc + SLCgwYMIEwqcwACeC3gM1imcSLGixYsYBboDQorUoncSM4ocSVIkkBsob/QIWbKly5frSKW80YDly5s4 + LWKYiXJCzp9AEZLgecNn0KMKJ0ywWbEEUaMY1zFFWnFCHEeO4kxV6JQn1Irr+kyZYoEB1Yt8GqhtcAdj + 15lfJ67rQaAuAR1xzx4UMGhtgyktLr5NmTehvFl2Cdwwq1ehgCl+GyDYenBwT4sMqCQmIItx44ToIjeo + ZxHeU4tzNhNgRPmzQG2iGRU2aNprRXiXNs/S5lrhOlei8d0+LXeZaiCteweI19fvoHgUa8OlSM/L5hvQ + lSvMJzpCdOIJJ/5cUJ1H+0QSjCIDniid8EQxXzZPmW1+4D3RlJK3v5yQQRXVYdQ30QQIiDaCQvsVpVAH + qk2SnIABMAGZX4JgkFCC9JVAymZ2BAahbxaIxs+F4BW0DiuqofIhRSUIEdkg4SCEIUJMlLIZKSSsSNE+ + ou0gY4kDMVCJanK4to5SDx7EQBGi8UYbkAK9EV9iEtCXkFRJErQOP1Q8soCHGN0XmTVTWaZgQSRIsNkX + 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 + 9t7y9O3y8ury8uDw8tzw8szu/hDa/lDk/ize/JLs/Ejg/mfn/jDg/g7a/HDo+nro+lrk+orq+ITo+HDk + +JTq9qzq9L7s/hXb/I7s/Izs/iLc/Izq/Ibq/Hzq+obq+oro9p7q/H7o/Hzo/Gjm+I7o9OXy/Gbm/GTm + +mzm+dHz+Hzm/k7k/kLi+lzi9Njw/Djg/DLe+k7g/iLe/Cre/Crc+Mnw9Mru/L3y+rrx/jng98Du/j7g + /Kfw+LTu+qru+LLu+LDu+qju+qbu/KDu+qTu/pXu/J7u/Jzu/nDq+o7q/Grm/Ijq/ITq/ILo/Gzo/G7m + /G7o/nbp/oDs+oPo+mbk+Ibo+nTm+l7i/IDq/IDo+nzo/ojs/pDs+KLs+Jbo9rbs+J7q+Jjq+JTo+nLm + +pDs+pbq+prs+mTk+p7s/FPj/EDg/Dze/DDe/lbl/kzk+qDs+qLs/rTy+Kjs+qTs+Krs/FHj+LLs/E7i + /kri/Pz8/kji/ub6/kbi/kTk/kTi/D7g/Drg/DTg/r70/ire/PH5/ije/ibe/sD0/iTe/sL0/sT2+mjk + /ETg/sb0/sb2/sj2/Ebg/sz2/Nr49PT0+N70/tT29ur0/Hbo/iDc+Jrq+sHy/LLx/vr8/Mn0/rDz/Pr7 + +vb5/Nb2+fL59ub08/Dz9Ojy+df09Nzw+Mvy9NLu/ML0+r3y+MLw/Kzw+Lbu+q3u/KLu/qDw/rn0/vz8 + /PX8/N749vL2+eL2/tb49u31+sfy/Ljy/Mz1+Lru+qzw/KTu/nDo/HLo/hzc9tHw/N7399Ty/OX4/t74 + 9OLy+MTw/K7w+rLw/LDw+rTw+rbw/q7y+PD3+Nry/jzg/pzu/Fvk/FbkAAAACP4AAwgcSLCgwYMIEwpE + lqEBrwYZRCmcSLGixYsYBSZjQ4PGhRASM4ocSVKkNgcoHaBDVrKly5cCRdFI6eAOS5g4c1akQBPlTZ1A + gw4M0dPBT6FIDVJAFvJig6IURIo6mrSiKFhBDgxrWvFpz6gXRRFTp4dehqoWycVZG4ccRq80wVptA6Mu + jDNy0SIUYIRtHD0onEK9CM6SXRgO8uo1KMDvWjZcFcJNqTghhSKHYViqvJjgG8dxdlmcjJLzwWiZYdCJ + 3JngO9CEqCIknbjrncyWwLVOKMoa6GldB08UhSY15N0J8/X1a2QZRdqmCf5ykNlBK+QKh4Ge91y4ZUep + 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 + EwpQFq82qDbLd0KLykZB5DP3jwo2YAMbVKBrOdcJBXhhggHUdOO7CQgAIfkEAAUAAAAsAAAAAGQAZACH + +Gzk/v7+/vz+8PDw/krk/hrc/gra/prw/Kbu/Izs/Cze7u7u/MD0/KTw9vb2/mro/ire/hba/qry/KTu + /HLo/Pr8+Pj4+nDm+lTi+kzg+pDq/Pr6+Hbk+G7k+Lbw+JTq/Pj8/Pj6+Pb4+PT49sbw9p7q/N34+sfy + 9NTw9L7s9LDq9vD19PT09Nrw9PL09PD0/lrm/hzc/KDu/J7u/Jru/Jbu/JTu/Fjk/m/o/jDf/hbc/Irq + +oLo+mjm+Iro+pbs+H7m+Jzs9qjq9Mju/Jjs/JLs/I7s+pzq9+Hz/IDq/Hrp+pLq+Jrq9qLq/Hbo/HTo + +njm/hja+nLm/lrk/kzk/ELi+lri+lji/D7g/Djg/DLe/NP299by9tby/jri/Mz09tDy+Mby9s7w9srw + /Mb0+Mbw/ML0+7fx/j/g+Lrw/kbi+6/w/Krw/Kjw/qjw/qHw/Kbw/nrq+orq+m7k+JDp+Ibm+nro+mTk + +qbu/ITq/H7o+oXp/oDr/oru+nrm/Ibq/pHt/Gzm/Ezi/ETg/Drg/DTg/p7u+mzm+pvs9rru9rDq9M7u + +Kzs/mHm/lbk/GPl+LDu/F3k+qjs/lTk+qju+q7u/rHy/lLk/lDk/rr0/k7k+rLu+LTu/Efh+Lbu+L7u + /ELg9r7u/r/0/Pz89sDu/uL5+qXt+vD4/Izq/ibe/E7i+tz2/iLe/iLc/sb2/FDi/iDc/FLk/h7e/h7c + /FTk/O759N7w/vr8/sr2+qDs9N7y9OLy+tDy/sz2+KLs9Oby/s72/Ob5/jrg/jjg/kTi/pzu/tD2+vj6 + /OL4+sry9/D3+OH2/Nj3+Nj0/M/2+Mry/Mj0/MT0/Lzy+MDw+rLw+qzw+qrw/qbw/rj0/sP1/vz8/ur6 + +vL6+uT2/sj2/PT6+tf09Oj0/Or6/tD4/LLw/LTw+rbw/LDy/njq/jbg+J7q9ur0+rzx/ors/pru+uH2 + +On2+r7y+ub39Ory/tj4+sz0+M7y/LTy+sLy9O7y+8Py8vLy/MTy9O30/nTqAAAACP4AAwgcSLCgwYMI + EwqsYMGBQwujFEqcSLGixYsCQ8Dx5w+BiIgYQ4ocGfLag5MP2FQgybKly4U4UD7wt/KlzZsUj8k8WROn + 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 + B9nADXSbggPCEbPjxbBtUX0QcMiisIP4Mo3IfFUAbmXTCKmAHXRbAnvepAJWpXOKWQRshMADAoVVwDrI + B+i8UkwhpjYArRGSC90CwdguuWxeGQ2cZiNEBFUorCy4YCJgYPZK0TE3Qj6h20e82ScCwKwxmPJiZz1i + 0uAmySiSbVP0xtvIFVkFLMaag4CT5L82DfDBO6trdR1YqEu4t09GUQpc4GKgoOl3TkeYIAhBqGPKlWQ2 + wi0iSAdo3IDqpXjKjRJzwIJ65jexgA46GwFE4zwkFphDZynw8p+Hpx8A6OoFRmB0n2yABQOIOgtorJqA + AAAh+QQABQAAACwAAAAAZABkAIf2guT+/v7+/P7u7u7+ROL+JN7+BNr+lO78kuz8RuL8GNryyOz8lu74 + +Pj+aOb+NOD+Btr+pPD8iOr6dub6+vr6TOD6Qt76ou74lur6+Pr4fOb4ZuL4sO74lOj89vr2jOby8vL2 + luj+FNz47/f27vX08PT07vT01vD00PD0sOr0puj08vTy0O78xvTw8PD06PLy5PDy2u7+VOb+MuD8cuj8 + QOD+dOr+OuD+Etr8kOz6kOr6XuT4gub4qOz4cOT4lOr0tOz+Gtz8juz4puz4nur+ZOj8jur8jOr6gOj5 + 1vT20vL2our+Itz8XOT6eOb+SuL8VOL8TuL8SuL8SOL6VOL6UuL6TuL+Jt78Nt703PDy4PD8Mt78Ktz8 + JNz+Dtr+DNr+Ctr+CNr02PD6uvD2xu/+QuL8rvD2v+74tO76svD6rO78qvD6qu78p+/6pu78nO7+ovD+ + oPD+mO/8mu78mO7+e+r+a+j+cur6auT4i+j6VuL8ger6luz6huj8ZOX6gOb8VuT8UOL+hOz8eOj+iuz6 + iOr+ku78dOj6dOb8ON78Lt76jOr6jur2rOz0uur4ouz6ZuT6mOr+Xeb+WOT6mOz6muz6nOz8YOX6nuz8 + Xub6oOz6ouz+qfH+VOT+tPT2tOz+TuT8RuD+TOT+tvP8/Pz++vz0wOz+yPb8Ot78POD6XOL+MN78WOT+ + 0fb+Lt78WuT82vf+LOD+LN7+Kt7+KN7+2vj6aub8aOb6buT6cOb6k+r+5Pr8aub6cuT8bub32/P84vj4 + 5PX6xvL8t/H+5vr80vb+INz24/L++Pz8+vv8+Pz58vn29fb09PT8yvT07PTy6vD53fX31vP04PD6v/L4 + zPD8svD4wvD4uO76tPD6rPD8rPD8qPD8nu7+nvD+sPL+wPT+/Pz+zPb+1fj83/j+3/r42vT86Pr55/b6 + y/P8v/L81/b25/T4uu78oO78ovD+uvP85vj+xPX87vny7vL03vL20PD8sfL6tvD6sPD8pPD4yfL6uPD8 + 9vz88vwAAAAI/gADCBxIsKDBgwgTCkzWoAGzhqQUSpxIsaLFiwIzbKtTx0yDiBhDihwZkpONkzbMgCTJ + sqVLAXVQ2qiz0qXNmxUzyDyZDKfPnwgb7LTRE6hRhcmS1aQodGdRjE+PWiTV4o03Z0slNpUZdSIpeHsE + jaEg1SI4QWgFqbu4FWVXiWaKyC2S5m3Zg0LSCiI0z2JbnhaDOZhb5A7ZuxL1ot3md6jdgxQQEC5i5zDi + hJwUCyJXkZnjikomF2FwWSI6zXIeF/TslCKzOpMdoCstcZhmZ64/Txwjeg1ticvy6hXiYSJrrhNJ3Jl8 + p/hvhek0wzOuO2GyC6LXPlfYYI7mcxKP/ruVGE30HtXbBUbT3CYrQfGAEzY4InpdeonJ1miGpRA+Ueii + zeHefQSdptgBqvmXIGyEOQAegQqRQo1mLSSkIEKkZCNaPhBOtIwhihHiD0IXHsSOHZPZkEGHE0Wn2D0k + VkcQBW+IVuFlpCQlUgNxaBbPQSUWZI5oEgwYoQACXCRAC97IMUpfGAGjGRxZ/fVfQQ1IIBpnFyUTjQQy + FLFGkhS1csCZB8gBjnMVJXOPZsUYZOVbwojWzUWkkFCJDHzyOUxFnKCJpjfFGGnQCIQoZogyWMoYwAg2 + TGYHlK6hUUSffdZhmUKBCoomJ8BYNKFi2g00J0HJqCHan0xVQwOm/phqWqannj7J1AEhUhrAqQMFgyJh + dayI3zFgwoopPugRJI4ctKIpRwtsJpSEZmYQZOVKyfAhWjoSkTLCBZMY2+ckaiRb0DzDNItmHOsYSkEE + moW60FArJSGaEOgxw5u4fQoxW0ikANOGumeu8SBCwWj2BkgwyVTHQMnsIZq8kCkxCL982lAoSxQU4w3B + B8Az4kGk2KYYfwJ9I1O1AjkjWhsYspMDxmGOddMy6jCrrhzOMGrQMonqdWMAypjBkRnCBvANc8scREIP + ktDszgg/kYLOPSDDAUtWLSg2zkCkUNBQA0+dMhk1cl5jB80SBGMoS8nAEgHIZhwsECnwohUB/pkKCSDE + XDlERcEvddBsRzpv25SBMzqrq060AowDzjiJL9TCNi08lScfNEuSRtKX4QyyN61UblEpaUSNMSW60ma1 + GSB/Y/pEpRSO8R7RzG4UBUnAQXAtN6mDsQPCmFtaAy00Lqg8N8UhriRqRMuiQCPAQyvwNoFjLAJ2T09Q + wJ0e8A3fLSmzR5+DtOt93+SIU85PybSwBlbr12///S6Rov/+/Pfv//+XYQgzBkjAAhrwgAUkG6pOwYkI + OPCBEIygBCXIiVOQzyfIQAAoCMDBDnrwgyAEISgQ4JxTTPCEKJzgKY6CgBC68IUhRIBSGpjCGqKQE0Yh + xQZhyMMXgoIs/jS0oRAhiEOgJGOHPUyiB0HRgACYcIhQjIA4cthCJVqRAAggiwDEEcQonpAT4rggTjKg + wSvycIQjiEoyKMDGNrrxjXBso1IQs8ax2fGOeMzj2ChgPPz58Y/1I0YtdvGTUsCDDuroY4fmIQ9PONKC + N1GGAx5AySbso4l+9IA4HMlJTxDSJtagpCgf4IR6bGp6pajFKDrJya/ZRAijFOUNMGCC9e0CHqzs5Cdd + koZYjvIJaGDGIhuZS06u8CYZ4IUvR+kHe2DyOf1oRTE5CQ8uQawlGWhDGZYpSiQcQ5H5U+U0HVmMaC3j + DbnohvQSkgEzSMAMpSDICCTATUrWgAgk/rgMMcAxTk+conUNqIEBBkqAeEokA00ogEIzATpSJOEW9XxA + IM7wTKD44xT9BEc8lkIKRgz0o4wJACka0AwQNKMBlkmDQld6gaVQgBpRiKgtTlBRmyBDmuMcBSzsEgwR + fHSgErjbCBDBBCYMQlikEMJKFfoAdhwkA5jYZj37wA7dJUQA5MDlOFuxzoVA4qcDTcNCBiGCsopgEAMZ + w1IVqoGaDgQdSIgoAXqwgpacg5/jlEfrClINCIA1EYdJBhPMKgImFCUDUVjrFVBgnWJcIqKAIINbKeKP + TY4THMBAT0DBagBTDIQChC3rM/ex1gI0QZgJoQAanhDRQ/xidgIQ/uc0dWquHnBWFc9sQGhFgNoAJMMX + pQXC25bBhxvU8wZzECNCatFPcXT1UAUAqxdiYK3dNoMgwYjFWstQAooEYxARhRlFiJlLvVaOFDzgLAA2 + pdvQguB7OigtBoyXjHSwgpuXqBx5qUmO2UXDC2DtAjSwtNv3EmQZqVjrKqZhkVQRwJeXMB45OjmKcfis + IhTYAWdVsJT2EtbABOFAaScw2YSMwAjGFaUaLFILeIxCHCO7yDU4a4HrEti9BqEAIEpbDYyQwhyHuEEC + MHHKozQAC2CFwAIO4mGzgpggSigtFHrb4IYU+SiY4OwGJtvksj4ZbBMobSOs+pwRBAGsItBC/lAKjBB0 + rGKtqXjGH0nhBM5+4Mq7YjPJIlHaPOCZReb4Ali5QA/56PkgyyDAWmWRBfxRAAqcTQF3Dn0Qta4VDyWG + EDY4WwEqy4nSOd5Bac9gvwYoAqxgYOykcayQJFxhrZ2u3w8464NMC6TLIviyQUixh9IugcxHQYdPf6oA + GEwE17o+1APWigWnsigZuOBsCMyFbIpcoLQ8AOdRlBAGsG6hrscGdVCesFZaSKNDyYgCZx1RkWpT5Bql + 5QWwccKMbv9UD7auLqsnkoxbrDUW87YJKZgx7IF6QQx+EXdCojGLpd4g4PlrQHo/2ofZuZsipIjvSikB + cZsw4weoSAUdS8B5cYpQgAhQgMIj/vybkWo7z/vGMEQAyZTdeprmPsF1vnFOkgYYg7BMYDnPe44Iwg6i + 40OHjAmIyoQJNC3pP6EAM0AwAJPu/C4BAQAh+QQABQAAACwAAAAAZABkAIf2hub+/v7+/P7y4PD+TOT+ + HNz+FNz+DNr+nPD8INzy0O78xvT8pPD29Pb+bOj+Htz+Gtz+Dtr+rPL8eOj8+vz6+Pr6cub6SOD6sPD4 + quz8+vr4+Pj4eOb4bOT4YuL4mOr8+Pz8+Pr49vj49Pj2xvD2muj89vz88fr61/b29vb02PDw8PD0vOz0 + tOz25fT09PT04PDy7PLy5vD08vT08PT08PL+XOb+Ftz8WOT+fOz+LOD8pO78ou78nu78nO78mO78lu78 + jOz6guj6YuT6oO74huj4duT2kOb4ouz07vT2ruz0wu7y2u782/b8gur6muz6luz4muz+cun8e+r4pur4 + mur2qur8fOj8euj6euj6eOb6dub+UuT8ROL6WOT6UuD8NuD41PL8Mt7+NN7+Lt7+Kt7+KN7+JN7+MeD8 + Ktz+Etz+ENz03PL+Ftr8z/X6vPD2z/D+POL8yvT8yPT4vvD+Q+L+SuT7tfD6tPD8sPD6svD8rvD8qvD+ + qvL+pPD6kOr6bOb4juj6fOj6eub6XOL6VOL+hOz+gur6h+j8lO76hOj8hez8fuj+jO78fur8iOz8aeb8 + TuL8PuD8Pt78LNz8iuz+k+7+mu7+Y+f+VuT8luz8YuX6pu74puz2tuz6nOz6mOz8mOz6ru76oOz8XOT6 + qO72xO7+VOT6qu74su7+svL6sO7+vPT8SeL4tu78RuL0yuz4vO7+v/X4wO78/Pz+2Pj8OuD8QN78QuD8 + ROD6cOT8UuT6cOb8VOL69Pj++vz++Pz67Pf+5vr8k+z6w/H6fub+y/b8juz6fuj64vb05PL85Pj6rO73 + 8PX+fur6zPL+6vry8vL6+vr89Pz63/b36vb83fj42fT81fb7vvL4zPL8zPT4xPD8tPL4uPD8svD8rPD+ + qPL+uPT+xfb+/Pz+3vj69vr68Pn+6Pr7xfP+0ff65vf85/n48ff60fT+9vz07PT4xvD8sPL6o+z+eur2 + 2vH+OOD+SuL+jOz8cOf33vT8v/P40PL7t/L6x/QAAAAI/gADCBxIsKDBgwgTCqRQYUOKDRVoKZxIsaLF + ixgFVshjyFA3ERIzihxJUqSqHChzdAtZsqXLlwIMpcxhiOXLmzgtUpiJkkLOn0ARVuCZw2fQowopULBZ + UQRRoxmZIq1IYRsDP26kKnTKE2pFAawaNdpXYerFBWLFLsDIdaZXit0cyHXQ761ZhJbSNrJ04mLblHYT + rpMy1wE9aXcp5tWryu9Ti9J+FHYgBXFihar0il1m8W9Pi2EmO/BxeWIwzY38BDbouWjTRZOlsCs9MRtq + N00fU8wmOg/tib6AaAZigmLr1QSb0ZtMz9fviW5QyzKuOyEFPqLTPZ9YwQ9qaBOP/k+kRrhwJeTbBVJD + LUHrQPEJK1gS3ST9RAp5UNdPCB/hM9E9uGffQMEslhYCyPXHmhOxBTPgRLTsg5ox/FVXEC3eiNbNgxT5 + colmlkyDkIIEVVPeXM4Ux+FE2KDGyogWDiQNA6LJQdtSI0njnWazsRajQE2Idgx6CNEigAAYCeCGHwiw + 0ldGyaDWh1QkBlBBEKJRkxEF1ASBiQN7IFkRNgiUWeYCKurUDWoo+NiVQfGIxgBGtFRDBCY25GlDPxap + YqaZfmBDJEEFanYJCAWR2M4UsZ1zUQMZ6qmnE4MG4OefZkrAmUVoabYWQQrSoodo2VhUAT8TSCqpE5ax + iCmm/k42hQCIT2pkoYmTHVLWfS4co6qqb1QagBuvYmoMohOhgNqGAw3FE0sU9CCaOxA2wwmev+aJCQbC + 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 + /Gjm/Gbm+mjk+Hzm/lbk/kjk+mLi+lzi+krg9OLy/Dbe+kjg/Cre/CLc9tTw/CDc+sXx/hLa/hDa/g7a + /gza/gra+Mzy/jbg/MHz9s7w+Mbw/j7h/kzi+MLw/Lby+rzw+L7w+rTw/qby/LPw+rLw/K7w/Krw/qbw + /p3w/KTw/KPu/Ibq/HTo/Gzm/J3t/Jjt+qft+pns+oXp/JXt/n7q/nTo/JTs+n7m+I3p+Irn+nLm+lDi + /IPq/obs/IDq+nzo/Hzq+ojo/ozt/pru/HLo+mzk+K7u9rbs9Mzu+Jzq9qTo+pzs/l/m/FLk/lTk/Dze + /DLe/Cje+J7s+KDs+qju+qru+qzu/lDk+q7u/E3i/k7k+Lju+rLu/kzk/Eri/krk+rbu/rjz9rru+qXt + +Lru/vr89NLu+L7u+qLs+qDu+lLi9sbu/Crc/Dje/Gzo+pLr/DDe/Hbo/DTe/Hbq/Hjo/uT6/Hro/uL6 + /D7g/PL6/tj4/iTe/ELg/iDc9rLs/Ebg/h7c/Irr/hze/hzc/Ejg/Izs/hre+tf1/Nv3+Krs/rr0/I7s + /r70/JDs+Kbs/sj1+O73/jTg+Nby/Mz0/kji/kbi/q7x+n7o/pju/Fzk/Ob4+oDo/Pr7/Nb2+fT59vb2 + 9vP2+uf39u30+OT09tny+s70+NHy/MT0+Mjy/Lvy+r3y+rfw/LLyAAAACP4AAwgcSLCgwYMIEwp0x6Ah + gwkKI0qcSLGixYEe4FCi1A/DxY8gQ4ac9ajko33uRKpcyXIhJZOPvqVsSbPmxAkwS860ybPnQAw5H+30 + SdSgu6EUGQRFSpFp0YgCPqBD98FpQqU5rSIUoObGATVanxYsd6DsgXIWscIMa3BWjrc5UIqVqMfsgUrL + Kqo1yZbgPENwczyCOFdhJbsHZuldSnHCrsA5DPUVOwvxAXZJGU/MBjnHgsIRxVnGM3mvzokMrnXGDFph + PssfUGuOKKZzndYR4dW1q8eDRNNCJYoDHPhRXtwKP1jO93s2QncWOqNFrnACHsvHrzo/WK/zjcm4Yf5Y + hhMReNgJlTproy7RXWXEMBSaV3iv8x7wyMUdtktH63yESkFmiDjsURQObNpl9Rw/ne1TIEUYLICYHtkV + 9J9Bw0H2iEcPbmaZGgBut1AEnd3T2lEgTYCOZaxZKGIAMHSmDn4ECSCARVGhQ4caFVI0j2X+MHXhQBMU + 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 + 7vai6vaY6PTI7vS+7Pry+Pfi9PT09PLy8vTy9P4Q2vrX9fTw9PTo8v5Q5P4u4PyS7PyO7PyM7PyK7PyG + 7PxM4v5l5v444P4O2vxy6Pp+6PpY4vig7Ph65vhq5PiW6vaw6vae6v4Y2v4i3PyG6vyA6vx+6vqM6vqG + 6vam6vPf8P4g3Pxq5vqI6PiQ6Pxo5vpo5PbC7v5O5P5C4vh25Pw84PpO4vwy3vpQ4PfM8f4i3vww3vwq + 3vTU8P4c3P4a3P4Y3P4W3P4U3P4S3PrA8fy78vLO7v484Piy7vqx8Pa+7vyn7/qs7vqm7vyi7vqk7v6V + 7vye7vyc7v5w6vyW7vqQ7Pxt5vyI7PyJ6vxu6Pxw5v526f6A7PqD6PiC5viA5vpw5PpW4vqA6P6G6/6O + 7Pio7PiY6va27PqO6vqQ6vqS6vqY7Pxg5Pw+4Pw44Pia6vqc7Ppg5Pqe7P5W5f5M5Pqg7P608/xX5Piu + 7PxP4v5K5P5K4vz8/P76/v7E9P5G4v5E5P5E4v7c+P4s3vw64P4q3vzy+v4o3v4m3vxA4PTm8v4k3vxC + 4PxE4Pil7PxG4v72/PxK4vii7Pzb9vrn9vfD7vx66PbY8vrN8/p45v7O9vbw9fzF9Pyy8P6w8vz6+/r3 + +fzW9/r0+vjn9vrg9vTt8/Lq8PfQ8vTa8PrE8vy+8/TO7vi67vq48Pyr8Pqu7vqo7vym7v6g8Pyg7vya + 7v649P78/P7J9v7k+vz1/P76/Pzj+Prt+PjA8Pfb8/rV9P7W+Pfy9/zL9fy38vqs8Pyi8P6u8v5w6Px2 + 6Pzf+P6x8/zo+fTk8vTc8vq68Pyu8PjG8Pq+8PbK8Pq88vbo9Prb9fx06Pq28P6e7v638/74/Pbs9vrj + 9/699AAAAAj+AAMIHEiwoMGDCBMK7ERBg0MKCiNKnEixosWBHszEibMM4sWPIEOC9Heg5IFlnUSqXMly + YRyTB+KkbEmz5kRgMEvOtMmz58BgOQ/s9EnUYKehFIHmREqRadGIAoRduiTMaUKlMK0iFNCrWrVeHp9K + 3DWt7LQPFrGa1HpwWZ+3fVCKlRjB7LQIpyqq1VmxFty3M4DNjWi3rD+9QdkSBMbmbx9Cggcn9Fd4GjuK + GhJTZOG4TwTJCrtVXqdYYOalEzXE6XwZdMJelcOl1ixRWWczrhUOq2s3gjaJp7NKNEHD8YxhuRUKq4wN + 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 + N4yoAT3QxAtLMMAwjVf2Ak1oAjTCXRSGRPnZEnGav99oEECfm+YqAbSOcQ4SCuCgsGhgOc895YfConXo + PAEGL4KKhiIUFek1AUYDUDAAL+78KQEBACH5BAAFAAAALAAAAABkAGQAh/aI5v7+/v78/vLq8v5K5P4a + 3P4K2v6a8Pyo7vyO7Pwu3vTC7vy+9Pyk8Pb29v5q6P4q3v4W2v6q8vym7vx26Pz6/Pr4+fpu5vpO4Pqs + 8PqU6vz6+vj1+Ph45vhs5Pia6vz4/Pz4+vaS6Pai6vac6vzl+Prw+PTk8vTW8PTK7vfk9PT09PTa8PTy + 9P5a5v4c3Pyi7vyg7vye7vyc7vya7vyY7vyW7vyS7vxa5P5w6P4v4P4Y3PyG7PqI6Ppm5Pqg7viG6Phw + 5Piq7Paq6vTQ7vTu8vyW7PyU7PyS7PyQ7PqY7PqW7Pic6vx+6vqW6vam6vvB8vx66Px46Pp+5vTo8v5W + 5v4Y2vpw5vh+5vTm9P5M5PxK4vpY4vxE4PpW4vw04PzS9vwy3vbT8v4W3P464vzK9PnA8PzE9PzC9P4/ + 4fzA9Py28f5G4vqy8Pyq8Pqw8P6q8P6h8Pyo8Pym8PqM6vpo5PiQ6vqE6Pp05vpg5P566vqI6vqo7vyC + 6vyA6P6A6/6K7vp+6PyE6vyG6vyI6vxu5/xW4vxG4Pw44P6Q7f6c7v5i5vxi5fi47vbA7va07Pqe7Pig + 7P5Y5Pxc5Pik7P5W5P5U5PxT4vqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5PxM4vqy7vi87vxE4v699Pz8 + /P7X+Pw64PbE7vqk7vxI4vpu5Pa87vrY9f4m3viU6P4k3v76/v7p+v4i3P4g3Pi07vp45v4e3v4e3Piy + 7vxY5PTe8PyN6/7F9fTg8Pz2+vfv9frK8vTk8Pzs+vri9v76/P464Pbe8v7g+f72/P7K9vLy8vr6+vj4 + +Pzn+vr2+vfp9vrE8vza9/jU9PzO9vjI8PzG9Pu88vq28Pys8P6o8P629P7B9P78/P7b+Prd9v7s+v7H + 9vz2/Pfx9/rS9Pzx+vrm9/jb9P7k+v74/P7Q9vjM8Pq68Pyu8PjM8vyw8Pyy8v424PzW9vjE8P5E4v6K + 7P6a7vze9/bK8PrG8vq+8vy+8vrA8vy89Pr2+AAAAAj+AAMIHEiwoMGDCBMKLMXMQjMLFRRKnEixosWL + AyugiRPnjAWMIEOKFInmgMkDvUaqXMmSYJyTB+K0nEnTYgWYJkvV3Mmz4AacB3T2HIqwlNCLFoAevbiU + KEUB0jqRkoYxKc6mEgUo41iGmdOKYCSIlSAPqdKLaACpBWQN61eDmcaKTWbRKky3CIetVWsj4luFcsWG + q3uWYoVsewEl8vsXYa/AEpxVtHsSr8FviQFlaqywHOROAihSzilaRuZhnBVSg+xNdGGJZTIzSK3wV1y5 + mUBMHB10ookbiW38oq1QGmRqu18XvZYZDHGFFTpBLieRt2WB5jLHYfz8YAnI3Kr+KzfITE7mEt0Vlnoc + GH1C6wpbZYaXXqLnwNu4F4SPMGniRNTVp1BYgZny3ngEWZPZGQJKFMJtY2VC10H8GWRCIokdoFuDCt0D + mTL9IRhABWtkVlZj10HXD2SoGVQhQcRkNkeKCAkQ2kXekEKKOhNeNAxkpFCIoAUNnIdRKebEoQcPpNw4 + ETKdRBmlKfpJVEo4kLlH0IsCkZPZNhiN844eZJJpTUXhSCklKcg4OVE5EIqV337jPWPDfwGKdgYPZZZ5 + QJUHKaOmmuGAY5E8kDm3pXKlQJFZGRUx00oiffZZA6AGoTPooDyKtg1kww3001UEXZghpgSVAk0DlVba + VkX+yJCy6ZreoDrQd4ENJipQqb6TGVUSjbPGH62W+cc+thr0izqzSskNOhJVwACLGfE6EGaJSXCdBcHw + WSyZcrQI0jC9NBulMnke9GNgoA300kkyCVRBHJnJklAF39jwLZk33EMjdLGa2wkYGwYKGbQC9QJTSgKZ + klk/RUGDwL5LlpGsRSCYIjAp3rg5kG2BnThiLxydwVgvid0QakEcXEPst3+ssTJN5VAjcC/2GmScXAjL + 2xAzRzm8F3IFMeMlxXFoyVMpspRrLroFCcDNWOFNJIAEa0nAXQXE0EBxIvL8u1IFAZtrSsECIQNGmxVV + AIYa8nAHTDsU/2Fyar9obC7+mx6vJAAUL3/bTrqplSPo032LJMABFM+g9HOllMCNuT2vpMy+gIBxcWMV + 5Dhray1tU+wfDKDNYQC/gLFp5SpdXikCJpyeV5rn0lSBImXa4K/sCsmCTM40CQAGA/IkzvvxyCcvETPN + OOD889BHL/30zXjlVCkWTK/99g5ANBAzwHgQgQHkl2/++eijP8YFsfcUixw5PCD//PTXb7/9OcihWzMe + pO///+krxOZAIof7GfCA90NAKRwwPgA68H8RsN5O4ofACh4wBxVoRgMfyEHz7UBsF6kABS1IQvrlgBnM + uEIHV0g+CgxFAiWMofwQEJFfUGAHLHRgBCjQo52AAH7+MqxgDhDAgaNkbwXLSKISl8jEJi5jBStwwABH + UooKoPCKWMyiFrfIjAqAUHlgNMgXw7gSC7RhDxkwnd+wZDEyjigaXSiAHC2hRpFUoA8uyGMUpiHB45Xi + Gz6QoyAL4IaZqCGPiHTBDVQwxcaYoAezGKQgeTCTBiQSkYvAxDhOxwwhhEGSg2zHTKBwyUQ+YB7NqE8F + 3AEKUA7SBcYDSQV4UMpE+uEcfUQRMS7gSkFCgAncGaNBSPSAWiJSCdAQ5kg4YAdX9LIAt+hAFo7yiwwM + IgMhqAgIOqGIJpVKBsbM4yLasMmvMKMWiHhmAbhAhI8M5BmTgIA8F3GxClCAAPj+1EMwv0HLcOagGu7k + SQXE4AV1nuIRDihIKX4gz4Z2YiBHdEAzGAMFfFq0DcMMRg7C6QIeHCOXLIGGLV7wTFeIoAhYgUY8GirP + GgyEA3RgAxsSsDIYWBSfVYDGQULwhkVwNAbAUKZBOPABCKgzCL4AVCkCwVJ5ziYApUgAGaZKhgQM5Aw3 + xacGQCqQYRiBow9oBAdUUgFRpEGdGEhBKhEyDR001RB+KQUbqEoGNgiFGYXIqhbOkZBStKIJHKWAO7ga + rXPkQZ1fGMIKFMKMeDYVHxmh61QlSI6sEiAKa0VIBaBQzHAmQAUgtEAPaPHMWQBgACmqRVMhgIeAMkOy + ZEj+qLwIYVlHTOQZCPCpMReRjVgShBkaUGcdWEDYUo2iqV/YxW9hK1vscCKriyinREqABI6ugSLNOEQv + D7GA5qpnCat9BXcswNyC2DSrfNhcKcAgBWP+gUYWUAUoFTCCxVaEGG5lKRuKsJ/yEuQZi8iqJU5gk2t0 + NpFN+NcHBkkLLFBhivZc7SOaQl7Jelcg+rAsLwJKERPA4JLXaBsSdACLRYihuAhxx2oZceEAVJiuLban + ZadxJGIkYBEPaMMUq2gUkFjAEE3VAREoBFv7FqQVlqVAiyfCEO/9ZROrnQKHIVrkg0TVso0gownS0NR4 + EJjIkjVyQUxgiay6ABhgLAX+HVZLAky9mKpiLkgGLBuJRqbmG/lt6BZa8J4qIwTAWeWECpJXgUKsVhQK + efNU41wQa1j2DlPmUDRW64MlUznM0ImCZddxvGdwIsgoqI6fE/INLWT1AZbuTipW24NI9xfT6qmBZUMh + VJ6YgAxNJQMVdjPqhJigCmZGc4NKcYfVPgG+vU5IGyyrBDv3pBV5luclUr2lZPfnAVn9BF/rU4oHrBYV + k7E2QoJh2T4IyALRhsAVMitqC7etD1nVRK1ZUopm4LqhOjhGXfxLEWI816IumPdKsAfehiohtPymSCpu + WkgBNYMSOHABDZGS8IlU4A05yIGOOcQQYSo6thipgAUzXO3GolW85DN5rWRJjnKVMIMedGWDs1u+PDrQ + lRc030kFxhFTNvBirDm3nQOQGEUU/yUgACH5BAAFAAAALAAAAABkAGQAh/Z+5P7+/v78/vLY7v5E4v4i + 3P4E2v6U7vyU7vww3Pwe3PLO7PyY7vj4+P5m5v404P4G2v6k8Px26PqM6vr6+vo+3vqk7viW6vr4+vr4 + +Ph+5vhu4vhk4viu7vaU6Pz4/PzR9vaK5vbA7vLy8v4U3Pjv9/b29vT09PTm8vTM7vS46vSk6PbY8vLq + 8PLk8PLe8PTy9PTu9PTW8PLu8v5U5v4y4Pxc5Pp65v506v464f4S2vyO7PqU6/pW4vig7PiE5vh25va2 + 7P4X3PS+7PSu6vyB6vbO8P5k6Px66vqQ6vna9Pii6via6viW6Px46P4g3Ppu5v5o5v5I4vxI4vpw5Pw2 + 4PpS4v4k3vpE4P4i3vw43vwy3vwk3Pwi3P4Q2v4O2v4M2v4K2v4I2vq28PfO8fTS7vy+8vbG7v5C4vjC + 8PbE7vi87vi27vyu8Pqw7/qr7vyp8Pye7vqo7vqm7v6i8P6g8P6a7vyc7vya7v576v5r6P5y6vpe4viK + 6PpU4vpK4PqH6fpy5Pwo3PyY7PyI6vx86vx66P6E7PyA6Pp+6Pp86Pp85v6L7P6S7vxu5vxO4vxC4vw6 + 3vw23vyK7PyM6vqa7PqS6vyM7Pik7PTG7Pau6vxk5f5b5v5a5Pqc7Pxf5fqe7Pqg7Pqi7Ppa4v6p8f5Y + 5Pim7P609Pio7Piq7P5O5P5M5P5M4vxM4v629Pz8/P76/v7G9vw74P7Q9v4r3vpo5PxE4P7c+Pzu+fiQ + 6vxQ4vxS4vxU5PrK8vxW4vyU7PTs8v7m+vxW5Pxa5Pbu9fyS7PTq8vzZ9/Tc8vp05vp25vjj9P76/PTg + 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 + hBoOUCsnnlERbGqTlYewUSgwSYsfGCN20lDEX4NgEAIX1sACycZif+Hcg1DDHvXsZDZwyIMc0EIPLCjx + Qdrx102AOHntvWYhFquNj2QnCjgYQ+xeQeQ6SsMGfy3DQTxc1hsHQAmLRYJ5tUWBhzjGDX/9AV+ZTFYn + v+IXi/WGIEsgha+iAQUI4TL+AZwcAFxwQq0OoKr5ePDXJZxRzWwOgBsWawGaygcb//3pLmBwvRznRw9q + 7cQyzJfYv4pAIXhWSC8WO4Epd6cbf4XClCOdEGkwVK2fHV8DShFdFkSE0wnBxmJxkOfoyOGvlT61oRHy + ijssVsjIwwUBvkoAd0gE1Xc5ApybqqBXTOCvqdAQsBMyhsWC4ps0UUKgQzqMVg9k2QjBwB4WizD5vMIR + fz0DRbCNkHYsdhL/acC0DSoBS1971kKZhIKhfaNdhzQH1WAXvBUSjzer9Aj0XkkD0BvSoOq7wBZ5r0o1 + mW45fGITbxAfuTsdDiQgYQx+TlwdJ97phggyNLt198ctQ0KBkI98J9IgbFlVkfGTrygJhf2Fy3UijXcQ + VRWW0OfMVRLDE4zgBCn6T0AAACH5BAAFAAAALAAAAABkAGQAh/aO5v7+/v78/vLe8P5M5P4c3P4a3P4M + 2v6c8Pwm3PLY7vzE9Pyi8Pb29v5s6P4e3P4O2v6x8vxy5vz6/Pr4+vpu5vpG4Pqs8PqY7Pz6+vj4+PiE + 5vh45vhq4via6vz4/Pz4+vj2+PbC8PaY6Pz2/Pzy+vrh9vTU8PLq8vTA7vSw6vfq9fTw9PTW8PLk8P5c + 5vxW5P587P4s4Pyo7vyk7vyi7vyg7vye7vya7vyI7PqC6Ppc5Pqk7vqe7PiE6Phy5Pik7PTs8vao7PTI + 7vS67Pza9/qa7P5y6fx86vx76fie6vao6vx26Pxy6Pp66Pp85vp05vjV8/Tm8v5Q5PxC4Ppa4vpQ4Pw6 + 4PbO8Pww3v403v4u3v4q3v4o3v4y4Pwo3Pu98f4g3P4U3P4S3P4Y2v4W2v4Q2vzO9fi/8P484vzL9PbK + 8PzG9P5B4v5K4vq68Pq08Pyy8Pqy8Pyv8P6o8fyq8Pym8P6g8Pyk8PqS6/pm5PiL6Pp86Ppk4vpY4v6G + 7P6C6vqK6fyW7fx+6vyC6Px66Px06PyA6vyC6v6M7vyG6vyI6vxr5vxK4vxA4Pw43vws3P6S7f6a7v5i + 5v5W5PyY7Pxi5fqq7vqg7Pim7Pay7PyY7vq07vpg5Pxb5Pqk7Pi67viu7P5U5Pqs7vqw7v689PbG7vi+ + 7vxK4Pa67vxE4vTQ7v7A9Pw+4Pz8/P7V+Pw83vps5viS6PxM4v76/v7l+vxO4vyU7fvE8vxQ4vyR7PxU + 4vTa8vyO7PxU5Pqy7vTe8vr0+PqA6PrM9P7K9v74/PTi8vrr+Pzn+P6A6vjc9P5+6vbS8PjI8PLy8vqo + 7vrQ9P649Pr6+vz0/Prl9/jx+PT09PXq9Pzg+Pja9Pbk9PzA8/zU9vjC8PzM9vzI9Pu28vq48Pyw8v6s + 8vys8Pyo8P6k8P7D9v78/P7f+f7p+vrJ8/r2+vrO9P7R9v76/Prw+Pzr+vji9Pbg9PjM8vrX9fi68Pbw + 9fqm7v566v444P5I4v7b+Pj0+Pf09gAAAAj+AAMIHEiwoMGDCBMOnCCt4QSFECNKnEix4sIzESKceWix + o8ePHs+MGznOHciTKFMSjEByXASVMGNSnNBypMybOA3SrJmzJ86dLV35HEqwFjFi2CwCJSm04quM3DgS + hZiulNVSSWfWHNd0ojcEYBGc6ToV4bmrpViR0FqTLER4d8IioOO2bEFWaEuZnLh0ZF2ErkrJRXDnr12B + r/KWijeRwlbDBrENRlDqcMISiokJkOi4Ledxg++UsJywiOJanz1H5DaZGOmEH86iPSc1YeegEYOZG0zn + w+uEtRTzi3ibKURXCyYX+Z1wAjHFoxUW9wvx2OQIm5kjjKd4r+3HCif+gJuMTLtCd4rVSQefUPLgyuYT + TsOLVvN31Qcp0AkdPT5Cfoqlcx9uCJ0xmTf+hSfbVef4lh97BsHDAG+1DQUZRMHl9QpC03F1kCusKGcX + BQ00IE1HrjyXV38EdfgXMtddGBMFPjwghh/MVBgRZnm5phOEA0kTwWSMVeTKMREkIggbFOVxwJMHjAHF + ChYllpd6BfXlYUFFTLaARdWUksiYY54hkTSOQAllGB6EQBE79DGY3UJABqDfYAxMQxEFwwhCJpl0RTRB + mmpCecUpJ0qETYBZ1qnGZN5BNIEJd/z5px06HlRJoYXuoIyMAUywoFVqEaRlQXANNk6mBR0pjqX+lo4l + 0QSBiMEplGIIU01EyCi2IZ08DRTYZFkpFMw2kcBKZiTfgCpsCzvcCmUCQFCgkCtnQAdsSwRRM1k0EFEA + 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 + WOT+SuT6auT8Ot76UuL6ROD6zPP8NN78Mt78Jtz8JNz+ENr+Dtr+DNr+Ctr02PD20PD+NuD4zvL2zPD6 + wPL4xvD8uPL+PeH4wPD+SOL6uPD4vvD+pvL8tfH6tvD8r/D8pvD+pvD8ou78luz8juz8iOr+nPD8pPD8 + n+78nO7+fur+duj8lO76re76ien6XuT4jOj4eub8gur8euj8cuj6gOj6dOb6VuL6TOD8iuz+huz8fOj8 + gOj6fub6muz+je3+mu78ZuX6mOz2tOz0yOz4ouz4mOr2tOr8QuD8ON78LN7+X+b+VOT4qOz4oOr6nOz6 + nuz8V+T8U+P4quz8UOL+UuT4rOz6sO74sO7+TuT+TOT4su74tO72vO74vu7+uPT+/Pz+x/X8QOD2xO76 + qe76pe7++vz0yu7+2vj00u76YOL6YuT6ZuT8ROD83/f4juj8RuL8SOL+JN78SuL+Itz+4vr+INz+Htz+ + HN7+HNz+Gtz6eub04/H87Pn++Pz62vX58fj8gOr81vb+NOD45fX8wfP+RuL+rvH8lu7+mO7+1Pj8nez+ + tvT8+vv8+Pz69vr39fb29Pb66vj8zvb28Pb06vT43PT24vP60fT21vL40/P6yPL4yvD4w/D6u/H8tvL8 + svD8q/D+pPD+vfT+0fcAAAAI/gADCBxIsKDBgwgTDtR1bsI5XQojSpxIsaLFgQLI9evH7dzFjyBDhiSX + q2QuciJTqlxJ0GTJfixjyqwowGXJmThzGjxnM5fOnwnPqWPA4CJPm0CTEmQwpEsXKkUrHnX5UVhJYRCV + RtSFwIBXAxmyTpxq8qI/O2jtoNSq8ByNrwbKwJPa0+K1tGjDiWW70wxcAz8mUCR7k6KuXXjR7uVLUBer + vwY4DK5LcUViO7sYJ1Rz5m8YEWMpS5xw57IHzQgnaIDMKTTSidwu+0Od0AS1v9RGSCTsU6KHcInDoaON + 8FwIyFg8thWt0MVlYcQTlgjz9wy9iLwlsrsMQUD0hCgg/ueIWpz5wXP9Ll/7nlBdJMi1lr9OiOxyZvYJ + FfiFSwp0+fnnmWMafgkxsAhkiiw2UHYJxZbYbAQm5EQCfxXgzX9UIfRbcMOxpeBEunQAGQXKFcSgQYdd + hgxfEwwl2EUkVPDXA/acZ95A2yUGE1sYIOKANInMUyJFREAmCnkL3hjAORCo99E15hwAiAsVcVLAlQVI + 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 + 9PL09Mru8O7w9MDs9Lzs+fH49tjx8/Dy8uzy8ujw8uDw8tzu8PDw+tf1/lDk/i7g/JDs/Ibq/Ezi/mfn + /jjg/g7a+pTq+mzm+mTk+lTi+Yrq/hLa+H7m+Izo9qzq9OXy9MTs/iLc/Hbo+ojq9qDq/iDc/HDo/Gzm + +oTo+Jbo+Ijo+Ibo/GTm/GDm/k7k/kLi+nDk+mrk+l7k/EDg+l7i9Nbu/Drg+lLg/iLe/C7e/Cze/h7c + /hrc/hjc/hbc/hTc/hLc/gza/gra/gja+rzy9sjv9NLu9Mzu/jzi/LXx+Lfu+rDw+LDu/KLu+qzu+K7u + +qju/pbu+qTu/KDu/Jzu/nDq+prs+pLq/Gbm/Gjm/I7r/H7q/HTo/G7o/njp/Irr/oDs/Ijs+nvn+IDm + +mLi/ofs/Hrq/Hro/o7s+JTq9q7s+obo+ojo/HLo+m7m+Jzq+pzs+KLs+qPs/Fnj/ELg/Dzg/Dre/lbl + /kzk+qjs+KTs+Krs/rjz/FDj/E7i9rbs/kri/kjk/kji/kbi/kTi/Pz8/vr+/vr8/uL6+pTs/jDe/N33 + +o7q/ize+orq/ire/ije/ibe/r70/iTe/HTm99/z/sH0/sj2/ETg/Ov6+un2/Ejg+sLy/vj+9uTy/sr2 + /Eji/Eri/tT4+uP2/Lzy/qDw/IDq+sny/rDy/Pr7+vb5/Pj8/ND2+vT6+Nj08/Hz8u7w+t329Ozy9Nrw + +sDy987x/Lny9sDu+rnw+LLu/KLw+q7u+qru/pzu/KDw/J7u/rz0/vz8/uT6/OT4+N/1/sX2/PP6+u34 + +Ob2/tr4+uX4/MHz/qLw+tDz+L/w+LTu/q7y/M31/nDo/KXu+OT0/IPq/rPz/Nb2/K3w9u319N/wAAAA + CP4AAwgcSLCgwYMIEwrUMA3GABgkKCicSLGixYsYBabgUaZMBWsSM4ocSTJjqgkGUhrYEbKky5cwKZhR + aeBHS5g4c1psQNMABA06gwpFuKLnz6FIFTZroOFmRWpGGySdWpCCjiuYmgC9CJUmBKkY29nS9SoV1Yqp + jmxYu8GHWYtdVX7FuI+ZXWbtzlJsVoTthjDSuEa9qE7fXWag3upF2EyM3w1WnCaMm3Iu2l2HmelbrDDV + pccb5Fk0Mbiiu8zMdnFWmG3M40zOKpL2CnZiM1uZ9albnZACB9CUZJee+Aq1Md4Kh3B5zMUXxdlyayd0 + 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 + ullDRAEKP9V/Fon3RbSBV3NmvA1GEIQ2bCorkAMXG0MEhr2TsueMqJxHIEzlXgAsdBWad+JFF0kzXjvZ + G+w86UqJ7GTjAHWhpCIEd5hrHLJXdapRlQRJClFAAAAh+QQABQAAACwAAAAAZABkAIf4buT+/v7+/P7y + 7PL+SuT+Gtz+Ctr+mvD8qO78iur8MN7u7u78wPT8pPD29vb+auj+Kt7+DNr+qvL8pu78buj8+vz68vj6 + cub6ZOT6SuD6lOr8+vr4hOb4eub4wvD4uvD4kur8+Pz8+Pr49vj2nOj87Pn0zvDw8PD0xu70vuz49Pj4 + 8Pb09PTz8fL27PXy7vD+GNr+Wub+HNz8pO78ou78oO78nu78lu78WOT6wvL+cOj+L9/+FNz8hOr6fuj6 + bub6YOT6muz4hOj4qOz2sOr06vL0zO78juz8jOz8iuz6luz43fT4nOr8jOr8eOr6lur4mur2pur8dej8 + cuj6dub+Vub+TOT81Pb6dOb22vL8SuL6buT6bOT03vD8QuL6UuL8POD8NOD+Ftr40vL21PL01PD8zfX+ + OuL2zvL4xvD8xvT2yPD8xPT4xPD8wvT+QuH7tvD+RuT7rPD8qPD+pvD8pvD+n/D8nO78kOz+eur6jOn6 + cOb6YuT4juj6euj6ou38fOr8euj6huj+f+v8gOj+iu76gOj8fur8gOr8gur+kO3+mu78bOb8VOT8SOL8 + QuD4ru72wu72suz02O74oOz+Yub+VuT8YuT8XOT6p+74pOr6qu7+VOT+sfL+Uub+UuT8UeL6rO7+UOT+ + uvT6sO7+TuT4su72uu74tO74tu74uO74vO7+vPT8/Pz+0Pf8iOr4kuj64vb8ROD++Pz+KN7+Jtz+JN7+ + It7+IN7+INz+Ht7+Htz6zvL8VuT+3fj8hur6fOb6oOz8WuL8lOz83Pb+OuD+OOD45PX+RuL+iuz+wvT8 + luz59fn89vz48vj29Pb08fT28fby7vL8xPL06/T44PX81/f23PL05PD41/T80fb4zPL8yfT8uvL7sPD+ + pPD+uPT+v/T+/Pz+2Pj66fj++v761/X+4/n84fj46/b+yPb04vL6svD8rvL89Pr+NuD+iOz65vb++P76 + 0vT67fj62/b6xvL25vL8vPL8sPL6yvL24vT6vvL6yfT8vvMAAAAI/gADCBxIsKDBgwgTChzB4sSCEyw2 + KJxIsaLFixgFWuAiBgYAaBIzihxJMmMrRgZSGrjArKTLlzAFbuCh0oCMljFz6qz4rKaBCCN2Ch1aEJpP + oESTIqzgYETIiy2OOlBKdaCIIFq0QHlaMWrNCFMxnlOn7lyrqhRb6SnAtoCesxa9qgSL0deou6N8oZ1Y + AULbAreWQJV60R0rvKPC7VVYAdffAhgqxCVs0RXiUawWJ2x16XEBDxZfUKZY4vIoV5oTktH1WBbOiaK/ + huWr5jKrZqmXdvAcrGLsubMVojM9LndCan7/QkhH8XdKuhNDhLscrpzxpSQ8+5Cs0PnP4Ait/pk+dz0h + tEePdWWDPRqhBdPqyitM4RkTV4PeoSNsZcZ0CfkJOYCBZx90155BpV2GGoAJTeLYX2CokFB+4BFUgTK2 + zcJgQhsI4dkTcOF3IEF2XabXhgkVocBjtMSDEIUIGXaZMgIsFiJGrUThGRbcFQTjQVeMt9gGI4zQ40Us + ZPCYDGMc9GNBCSIW314igHDGDoxkc2NFKHj2yX0ByPVcha2oY5o7IpWgjASdFGfRHxDECcEOgjB30Qhc + eAaJQWJ+Z9A5pl2RUTNmSGCooW7yZYmccp6RiQgXdWHLY2FYUFCf+gl0IXUhXFQBMZ0cemhmaeHAKKOg + pHFkQhX04dlb/gRhCh4xpp2YVjyjiCoqK1siNMOpp/5QTa8GDWDMY7ZUQ5BRXwU1kIyIsbGqe2zoqisx + FgmgxA7AyrnDEZYq1AoRnjHSYzRHvRaAZZf9N5EI2lirqzU4XvNDt3K+AQmYBDnwhWdpDKTCUU9FideC + rIIq76HqoCnSCJGAgm+cOPAy7UBGyPCYFpAGUAFNKhXQo5mXOXxQK+TkurAEo7hbEjSanDExBFTEQywz + VHiWykAU1HQBd4BeRq976qzMpi81wtRKERxwi28YSoRbEDbJtXUEXO780NEFK8BVIl7hXCzCFUZL4Eqn + OzFTBgYzI6PKfRUE8Rg7A23gAAstsOAA/lxB40VeQRWgE+rKypg8lAOnaDEzEFoSJEIjbeFwsUHsnlZQ + KyWEYzQ4f1PVCjRMvDPxDoSQE2I57fwiR8cVnTNO5wK5U+jKbU5OVAXrADOzMX+wHpMA1gy+sBm4GTeC + CZgs7rtLAmi+cjgul+eAJF5MTEpOvqwMDjooBtAKNSA4feoRObkibztX2F5eBV1gAez1MWWva8PdHzTC + Go3IWYn6GDU/avT1M4gI8HGDUSRNJ+e4AuwCyMAGOtBTG2CGBCdIwQpa8IIb4F9MKhDBC3oQg0eqQDQS + EIczmPCEKEyhClX4hgQUbygCYIMdDkDDGtrwhjjEoR2kFYANJGCF/kAM4gqRQCyYsCGHSExiDtkQAGaU + UIhQDGIcNDiSGSrxikm0Qys28MQoehGFcUiKFbFIRhtqsQJH+KIaTaiIpByxjHA8ABvOEoIjdHGNQIyD + IqxzOxnG8Yo79J3d7saCQhrykIhMpAOaUsSdtOKRkIykJCdJyUc+8JKYzGTdckADflDxIgIYBxvG8UkA + VYAXDyCAKp1QyokIgA6FiKUdYtHK3MTjF6rMJQFIBRNtxPKXhcgHPRpZHgvQQBS6zKUdchIOYP5SETlQ + lynxgYlk6pIBObmCM4F5gCtIMzcVMMcUrKlLQBzwJRWQwDaB2QBaXoccRyBnLqvAjiMRsyIV/jCDItb5 + SzisIDXMyAQn5EkAK2jABQQJAT9o4I9P5hMcZjinO7rBz1gqQg3fHEoF1FAJghLAD1ngSgUQEYOS9uCc + CBHAAfLAUjuglBxzqGghDmAOfsWkFUvIg0cr4QFnEQQOJQ3qlHo4gqZsIETaYKlS1WCQVljjADKVgOmE + Qo9hWIGgnBhCNA5SgkoEtaTtsAoCdKADCaAtAN1QKkt78E+DbHSfFe2G1F7CDG9UwaNHuMfFWhGEr5bU + DAOZwwMG+wAJDOQKamWpNybnDnDIVBFmyKin9JFKgkqBDJINQD38GgNA9EgHhH2ADgZSgRskNg+xUMg5 + GiBTG9Sjlt6L/gUhdrqKClkoEZxdj0BaEdrB9sgXp72DTXd7hRvItB0AnMgGjklQUVACoROxB2c18JQK + 9PYBr2nFHE47D4qEwA1w5ScT8bkJj75iHxpkBgX8Wol7WOi6Pg1ACQaRWCRkliAl6IRMAUsR9cqTAmi4 + L0HkwNlQ3GgD1wVPWhPLj3sOBB124Oc3KrIBClgzBqiwLULI4dWvUmCrBEFwb8ETgkIkdhB2wqcZjOvM + BljEG7osRRBc4OCBtOIGnA1YQUQc2gqZ4bRyaGUz/OFMbVikAgiIgSd6UI1aWoOzPfgmjwlboQqsNLHK + ygg52qGIG6ihxjYmSQUAwdksHGTKg7Vt/ixOe4PhMqYCYIYJAzgbBH6h+QEabsdpt5FJCzyAvdA1yJ01 + bAH6qrUQAmZQkv1qiiIOOiEMOG034mzLDgd1CvHdcYITEgJFnJgcDmzFETjLZw5tOiGITWwD3FyeeXA2 + Afd99GZscNp6MLACUuBslk094onE47SKSHRqgOpXGrBa1gphx2ntUT8/+/UBbVUIshPijh4kFh5zlU8r + asDZDzh42glRw2nhQOmkoIOzTsg0QsC9FE+rdRAuAlArkMDZJlH41BSpx2ldDKANcBYPrK4bvifSigac + mEEI/moljnERdidkvmqFB8IJHFRvYMThCcGHWrGJcDgcYhDdQKlyQwdOkY3ewMuwTQqcRYLxzXBQkyW/ + rrBhLhLr9jbgNK85aAmrg3LnvOQICO0cfi6UVqhgAmSdw1mJflNmLLIpKd9JQAAAIfkEAAUAAAAsAAAA + AGQAZACH9nrk/v7+/vz+7u7u/kTi/iTe/gTa/pTu/JTu/FDi/B7c8srs/Jru+Pj4/mbm/jTg/gba/qTw + /JLs+obq+vr6+mDi+kTg+jre+qDu+Iro+vj6+Gri+Fbg+LLu+ITm/Pj8/Mz19rbu8PDw9oTm/hTc+PH3 + 9u709PT09N7w9Nbw9MDu9LTq9Krq8u7w8uLw8tzw9PL09PD09O/y/lTm/jLg/Hbo/Eji/nTq/jrg/hLa + /Jrs+prs+mzm+lLi+HTk+Jjq9rLs9qDo/hvc9Nrw9NTw9MLs8ujy/Jbs+pLq+tf1/mTo+pDq+o7q+ozq + +JDq9qrq9qDq9OTy/Gbm/iLc/Fzk+mrm9o7m/kji/Fjk+m7k99by/FLi/DTe+mLi/ibe+lDg+kjg/Cjc + /CTc/CDc/hDa/g7a/gza/gra/gja9s3w+rrx9M7u/Lvy9sfu/kLi9r7u+rLw+Lju/K3w+qru/Kjv/Kbu + +qbu/qLw/p7w/J7u/pju/Jzu/nvq/mvo/nLq/Izr/HTm/GHl/Frk/ITq/oTs+n3n/Drg/Czc+nbm+lTi + +H/m+nLk/Hvo+njo+KTq9rrs+JLo/ovs+p7s+pTs/pLu+nHm/G/n+JTq/Gjm+pjs/lvm/lrk/F7l+qDs + +qTs/qvx/ljk+Kbs/DLe+Kjs/rT0+LDs/E3i/k7k/kzk/kzi/D7e/rfz/Pz8/EDg/vr8/sv2/Ebi/Dbe + /jDe/GTm/OL4/tr4/izg/ize+lrk/ire/ije/uL6+J/q/HTo+svz+Oj2/PH6+pzs/vj8/NX2/h7e+OL2 + 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 + MqcG+qxBNEQVADhoFg7oUa9up5LWxH6zIrRQrTzJeN9uKiEOarGIP74KVj60VcCfnYoGKJFYtl5EAMMY + LjUZTBFrCBMUGDBBa3HLAM2upiAYJuxYEKvWeuT0d9M85jAsIgA5KEETfyjGjQWSDs3+YSwxHuxYWMGN + 0lorI+NYhR7wAIIV47Z5hNDsPg6SZPYepLeJxQN/l4LU0khDsxiITv6XO/ON0ppDlMkwL1jRi5A1I8S9 + iaUEXTfoY7/eY012Rog8SvsOK0vnHBQeKh8izOX1kuaPHTYuAFlxAM3arM6OVgiNlyqHIZdmGJqVwG4C + PZ6XJvZEAKQAH/zqAG7hJdOoLa03PL0WNmjWDr4idULYUNqH2Q8YfvCrH9ra6AFTBB3vVWsk9nyjOmj2 + HZuC9URAUFo2qI8WDvDrIESla7wwoLT+MJIENDucaBu7IukoLYkbRIFsg/UIOe22Qj7R4RtRINhDpTNV + pE2RceTZ3v4d6v72fW6LgEOtB84mrwkxjZamiN8UYcU68EBlQ2OF1mWEeMRLOEqyaLzjMekyxiRBfiTP + EpYPFid5xOXwWcOq/J8NkENZ5fDRl5eEKQ2wpJVQFBAAIfkEAAUAAAAsAAAAAGQAZACH9ojm/v7+/vz+ + 8PDw/kzk/iDe/gza/pzw/KDu/Ebg/Cbc9Kzq/Lz0/Kbw+Pb4/mzo/jzi/g7a/qzy/I7s+pbq/Pr8+uf3 + +lzi+kLg+I7o+H7k+HDk+Gbi+Mby+Lzw/Pj8/Pj68vLy9qTq9pLm/Pb8/PL6+PT49PT09ODw9NTw9MTu + +PL29PD09PDy8ury8uLw8tzw9t/z+OL09O708u7y/lzm/izg/G7m+oDo/nzs/kPi/hzc/hrc/Jzu/Jju + /JXs+qHt+nLm+lrk+KTs+Hrm9rbs9qLo9O7y9Mzu/h7c/Nj2/JDs+pzs+pbs+Jzq9qzs9Ozy/nLp+I7q + 9Oby/D7g/GDk+nDm/lDk/Fjk/E7i+mjk9OTy/Eji+rnw+k7g/Ezg/DTe/i7e/ize/ire/ije/ibe/iLe + /DDe/Cze9tDw/hTc/hLc/hDc+Nbz/hja/hba/hTa/hDa9Njw/Mz0+Mry/MT0+MLw/MTy/MDy/kzi/L70 + +L7w+7Pw/Kzw+qzw/qry/qTw/Krw/Kjw/p7w+o7q+nzo/Dze/oXs/oDq/Hvp/GTm/Fbk/Ezi+nbm+Ijo + +Ibo+lbi+oPo/ozu/Hbo/pLt/G7o/Jru/Jbu+LLu9rju9NDu/JLs+J7s+Jbo/pru/mPn/lbk+qbu+qDs + +pjs/Jjs/GLl+KLs/Dzg+qju/lTk+qru/FLi+q7u/rPy+rLu/rz0+MDu9r7u/ELg/r/0/Pz8/jPg9sTu + /uL59sru+s30/Gbm/Gjm/Grm+Krs/sn2/vj8+Pj4+n7o/vb8+tT0/Ob49vL0+vP4+Ob2/n7q/H/p+njm + /s72+77x+nrm+u74/PT89vX2+PL49PL09un0+OP2/N349Or0+rzw9tzw+Nv0/NH2+NDy/Mf09sjw/ML0 + +rbw+rDw/qrw/rj0/sP2/vz8/ur6+tL0/sz2+vn6+tz1/Ov69vD2+vT5+Oz2/tP3+sXz+rry9tjy/Lbw + /LDw/LTy/LDy/krk/kri+qTu/nrq9tjw/jzg/jrg/Ibr+nroAAAACP4AAwgcSLCgwYMIEwpEdyLEgBDQ + KiicSLGixYsYBUZrNGYMERYSM4ocSTIjrUk7Uu4Ihq6ky5cwBVYgo3JHmJYxc+qsaKLmjgI4dwodOrCY + T6BEkyKkBQxYUIssjgITWQEdLaUY0QHBgmXIU4pRaxaYehHdplNUKDjAarGJjbc2KGAMq3LsxQqRDOg1 + 4AjYVbYJK0CAa6MWtYt0U9q1mELNXgNmoP0FfLBCLcI2goQEK9UiNEiPDbxZSznhDcw2Olhs0blikdAG + hGwubTDeZcKpSE9kLZbsRBZgQqtpQzvwI9SoKvKu6zvwCNjNZhcvOG0wYQjtKC5X3BzhiyShzf5kn75U + BGpHkxFu/9ndILANsDmRV3giFeZa2Xa3ToiETWhDus13kC2oafFVQesthhA0X8Bmh4AKAWMFaswolGB7 + A9GyAGxVHAghQdjcBhcBASK4n0EzhBHaG+l8qBA6UqAmSkIXIoQOALBFkp6LBkEhC2b8WKDeiQTB4EZo + YzjD40RPoIbDjgLVaJADHMDWS2noOACMdBVBIwRqwxwkZUEqRBBaAhgOhQ4TBOiQSDZQToQLah0alBh7 + Bp2AQWgR0CFSCe7MMs4tF6ECwaEQ6LCEkhcBowxq39hJJC0iwKYFlwp9oMQsr3T6CqEUVaAIoogSwEqJ + CqEg4ls6GFPQnf4KCuSCAqHxcJhFFdwyjqeenlMRLbqQSuondGBqUAWdoNbEqyeiowFsGcR5kDq+8Mqr + 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 + 9Pba8v5m6Px+6vqQ6viW6viU6vaW6PTq8vyA6PqA6Pp45vpu5viG5v5c5P5I5PxC4vpu5PpQ4vw23vpC + 4Pwu3vrN8/wk3P4Q2v4O2v4M2v4K2vjO8vbU8f424PbO8Py+8vjJ8PbK8Pq/8f494fjE8P5I4vi88Pq6 + 8Py48vq28P6m8vy08Pyw8Pys8Pyo8P6m8Pym8P6d8P596v5u6P526PqH6Ppg4viO6PiC5vp86Pp05vpS + 4vpK4P6G7Pqm7fyW7PyE7PyC6vxu5/xN4vw83vww3vwm3Pp66PyI7P6P7fyK6v6a7viz7va67PTI7vTA + 7Piq7PqU6viY6vam6vxk5fig7Pqc7Pic6v5e5vxg5f5a5Pqe7Ppd4vqg7Pxa5Pqq7v5U5P5S5Pis7Pqs + 7v5Q5Pqw7v5O5P5M5Pwy3v5K5Pi27v648/xF4f78/Pa67v76/v76/PbA7vw84PpU4vw63vTO7vxU5Ppi + 4vpm5Pzu+v7a+PxW4vpq5P4k3vuf7f4i3Pqa7Paw7P4g3P4e3P4c3PyZ7v4a3v4a3Pin7P669Pzg9/6+ + 9PyS7Piw7P7K9fLq8vrb9fzK9Pbs9Pp+5vjv9/404Pjn9fbq9P5G4v6t8fx36P6Y7vz2+/z7/PzW9vr2 + +vb29vXy9PTw8vro+PTm8vjf9Pbg9PrU9PjX8/bV8vbS8PzA9PjE8vrF8vi+8Pq78fy68vq48Pyy8v6k + 8AAAAAj+AAMIHEiwoMGDCBMKZFCixYAW69ApnEixosWLGAWSIAQOXBB2EjOKHEkyIzoJDlI66DGhpMuX + MAVOgKTSAZqWMXPqrEiipgMdDHYKHVqQnU+gRJMiRMeAAc6LJo4GNTkhpFKLExKRIpXoacWoNZFeRKfo + 1jgh665aDIWmLZpQGMGqFFsRHaACeAtcYGBV7UF0BNyicQMDqtSLZ5zlLQC0r9+C6NwIRlPOcUK5KelO + ZHBqcYFmUx8jzDEZDRiLIg5XxOS5wCfLogcqkSwYS2iFqcPevtzL8zMlsROiu1DaVcXcc3f/jdJ6Cuzg + ArsFFkzgG0XkmZUblOHAsw7r0Jf+bip94flA7D+1E2SwoTWS8ArZYZnsZttE9JoPAovm+RYG+AqFUJoV + XhmEn3oClWBIa/gAqBAD5ZTWYEIHCsdMa8MU6GBBM9DmFg3qUKjaQSZ04Vkz8Gyo0ARQlJaIiLohNIEg + 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 + + + \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 07ef37a890..82e9ded400 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -1,5 +1,6 @@ using HICPlugin.Curation.Data; using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using System; using System.Collections.Generic; @@ -18,6 +19,7 @@ public partial class ViewRedactedCHIsInCatalogueDialog : Form private bool _isLoading = true; private IBasicActivateItems _activator; private ICatalogue _catalogue; + private DataTable _results; public ViewRedactedCHIsInCatalogueDialog(IBasicActivateItems activator, ICatalogue catalogue) @@ -32,12 +34,40 @@ public ViewRedactedCHIsInCatalogueDialog(IBasicActivateItems activator, ICatalog private void RevertButtonClick(int itemIndex) { - //todo ExecuteCommandRevertRedactedCHI + var result = _results.Rows[itemIndex]; + var potentialCHI = result.ItemArray[0].ToString(); + var context = result.ItemArray[1].ToString(); + var column = result.ItemArray[3].ToString(); + var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); + if (redactedChi is not null) + { + var cmd = new ExecuteCommandRevertRedactedCHI(_activator, redactedChi); + cmd.Execute(); + result.Delete(); + _results.AcceptChanges(); + dtResults.DataSource = _results; + dtResults.Columns[5].Visible = false;//todo this isn't quite right + + } } private void ConfirmButtonClick(int itemIndex) { - //todo ExecuteCommandConfirmRedactedCHI + var result = _results.Rows[itemIndex]; + var potentialCHI = result.ItemArray[0].ToString(); + var context = result.ItemArray[1].ToString(); + var column = result.ItemArray[3].ToString(); + var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); + if (redactedChi is not null) + { + var cmd = new ExecuteCommandConfirmRedactedCHI(_activator, redactedChi); + cmd.Execute(); + result.Delete(); + _results.AcceptChanges(); + dtResults.DataSource = _results; + dtResults.Columns[5].Visible = false;//todo this isn't quite right + + } } private void handleClick(object sender, DataGridViewCellEventArgs e) @@ -53,24 +83,31 @@ private void handleClick(object sender, DataGridViewCellEventArgs e) } - //TODO allowlist - - private void RevertAll(object sender, EventArgs e) { - if (_activator.YesNo("Do you want to revert all these redactions?","Revert All")) + private void RevertAll(object sender, EventArgs e) + { + if (_activator.YesNo("Do you want to revert all these redactions?", "Revert All")) { - //todo ExecuteCommandRevertCHIRedactionsForCatalogue + foreach (var rIndex in Enumerable.Range(0,_results.Rows.Count)) + { + RevertButtonClick(rIndex); + } } } - private void ConfirmAll(object sender, EventArgs e) { - if (_activator.YesNo("Do you want to confirm all these redactions?", "Confirm All")){ - //todo ExecuteCommandConfirmtCHIRedactionsForCatalogue + private void ConfirmAll(object sender, EventArgs e) + { + if (_activator.YesNo("Do you want to confirm all these redactions?", "Confirm All")) + { + foreach (var rIndex in Enumerable.Range(0, _results.Rows.Count)) + { + ConfirmButtonClick(rIndex); + } } } private string locationToColumn(string location) { var lastIdx = location.LastIndexOf('.'); - return location[(lastIdx+1)..]; + return location[(lastIdx + 1)..]; } private void FindChis() @@ -87,11 +124,14 @@ private void FindChis() dt.Columns.Add(new DataColumn("Potental CHI", typeof(string))); dt.Columns.Add(new DataColumn("Context", typeof(string))); dt.Columns.Add(new DataColumn("Column", typeof(string))); + dt.Columns.Add(new DataColumn("_hiddenFullLocation", typeof(string))); foreach (var rc in redactedChis) { - dt.Rows.Add(new object[] {rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation) }); + dt.Rows.Add(new object[] { rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation), rc.CHILocation }); } dtResults.DataSource = dt; + dtResults.Columns[3].Visible = false; + _results = dt; DataGridViewButtonColumn revertColumn = new DataGridViewButtonColumn(); revertColumn.Text = "Revert"; revertColumn.Name = "Revert"; From 6a30d94a6ce02c9fdf0df52f2d1519be1b894641 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 28 Nov 2023 15:47:02 +0000 Subject: [PATCH 08/64] fix namespace --- Rdmp.Core/Curation/Data/IRedactedCHI.cs | 2 +- Rdmp.Core/Curation/Data/RedactedCHI.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Rdmp.Core/Curation/Data/IRedactedCHI.cs b/Rdmp.Core/Curation/Data/IRedactedCHI.cs index 814f407fde..d1688de1ff 100644 --- a/Rdmp.Core/Curation/Data/IRedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/IRedactedCHI.cs @@ -9,7 +9,7 @@ using System.Text; using System.Threading.Tasks; -namespace HICPlugin.Curation.Data; +namespace Rdmp.Core.Curation.Data; public interface IRedactedCHI :IMapsDirectlyToDatabaseTable { diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 6ce552d448..47cf1d2492 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -21,7 +21,7 @@ -namespace HICPlugin.Curation.Data; +namespace Rdmp.Core.Curation.Data; public class RedactedCHI : DatabaseEntity, IRedactedCHI { From 44fb9be1ff58f5a035bd4f5c714a5f381643baba Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 08:00:01 +0000 Subject: [PATCH 09/64] remove hicplugin reference --- .../AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs | 3 +-- .../ExecuteCommandRedactCHIsFromCatalogue.cs | 8 ++++++-- .../AtomicCommands/ExecuteCommandRevertRedactedCHI.cs | 3 +-- .../Components/Runtime/ExecuteCHIRedactionStage.cs | 1 - Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs | 6 +++++- .../SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs | 6 +++++- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs index dec70626c3..31d4b4b46e 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs @@ -1,5 +1,4 @@ -using HICPlugin.Curation.Data; -using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data; namespace Rdmp.Core.CommandExecution.AtomicCommands; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index b9233f8cb4..1affa409a6 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -1,4 +1,9 @@ -using Rdmp.Core.CommandExecution.AtomicCommands; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . +using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.CommandExecution; using System; using System.Collections.Generic; @@ -14,7 +19,6 @@ using Rdmp.Core.Curation.Data.Defaults; using TB.ComponentModel; using FAnsi.Discovery.TableCreation; -using HICPlugin.Curation.Data; using Rdmp.Core.DataFlowPipeline; namespace Rdmp.Core.CommandExecution.AtomicCommands; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 795ebf9298..bab329a100 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -1,5 +1,4 @@ -using HICPlugin.Curation.Data; -using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient; using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.DataAccess; using System; diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 68cb20266d..07ab275c81 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -1,5 +1,4 @@ using FAnsi.Discovery; -using HICPlugin.Curation.Data; using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index f5bc5500a1..d60237c197 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -1,4 +1,8 @@ -using HICPlugin.Curation.Data; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.UI.ItemActivation; diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 82e9ded400..dced3a1e3c 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -1,4 +1,8 @@ -using HICPlugin.Curation.Data; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; From 37746faeabf261e59087230742e3d3ac83d0f604 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 08:26:45 +0000 Subject: [PATCH 10/64] fix file documentation --- Rdmp.Core/Curation/Data/IRedactedCHI.cs | 10 +++++++++- Rdmp.Core/Curation/Data/RedactedCHI.cs | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Rdmp.Core/Curation/Data/IRedactedCHI.cs b/Rdmp.Core/Curation/Data/IRedactedCHI.cs index d1688de1ff..cb76840707 100644 --- a/Rdmp.Core/Curation/Data/IRedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/IRedactedCHI.cs @@ -1,4 +1,9 @@ -using FAnsi.Naming; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . +using FAnsi.Naming; using Rdmp.Core.Curation.Data.Cohort; using Rdmp.Core.MapsDirectlyToDatabaseTable; using Rdmp.Core.Repositories; @@ -11,6 +16,9 @@ namespace Rdmp.Core.Curation.Data; +/// +/// This class stores the redacted CHIs that are found during data load of via catalogue mutilation +/// public interface IRedactedCHI :IMapsDirectlyToDatabaseTable { diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 47cf1d2492..dbf097fbfc 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -23,6 +23,8 @@ namespace Rdmp.Core.Curation.Data; +/// + public class RedactedCHI : DatabaseEntity, IRedactedCHI { private string _potentialCHI; From ae8116ec816d286b21df9f5678978ecae75893f2 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 08:40:28 +0000 Subject: [PATCH 11/64] fix up tests --- .../ClassFileEvaluation/ExplicitDatabaseNameChecker.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs index 9be14362a9..b7a0a12825 100644 --- a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs +++ b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs @@ -42,7 +42,10 @@ public static void FindProblems(List csFilesFound) "PlatformDatabaseCreationOptions.cs", "PackOptions.cs", "PasswordEncryptionKeyLocation.cs", - "ToLoggingDatabaseDataLoadEventListener.cs" + "ToLoggingDatabaseDataLoadEventListener.cs", + "ExecuteCommandIdentifyCHIInCatalogue", + "ExecuteCommandRedactCHIsFromCatalogue", + "CHIColumnFinder" }); //allowed because it's default arguments for CLI prohibitedStrings.Add("TEST_"); From 3ceb878bc501f8f20183b810c1575f0658e3b42a Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 08:49:45 +0000 Subject: [PATCH 12/64] use correct list --- .../ClassFileEvaluation/ExplicitDatabaseNameChecker.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs index b7a0a12825..10c7d1f4e0 100644 --- a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs +++ b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs @@ -28,7 +28,10 @@ public static void FindProblems(List csFilesFound) "ChoosePlatformDatabasesUI.Designer.cs", //allowed because it is a suggestion to user about what prefix to use "PluginPackagerProgramOptions.cs", //allwed because it's a suggestion to the user about command line arguments "DocumentationCrossExaminationTest.cs", //allowed because its basically a list of comments that are allowed despite not appearing in the codebase - "ResearchDataManagementPlatformOptions.cs" //allowed because it's an Example + "ResearchDataManagementPlatformOptions.cs", //allowed because it's an Example, + "ExecuteCommandIdentifyCHIInCatalogue", //uses the RDMP_ALL allow list + "ExecuteCommandRedactCHIsFromCatalogue", //uses the RDMP_ALL allow list + "CHIColumnFinder" //uses the RDMP_ALL allow list }; @@ -43,9 +46,7 @@ public static void FindProblems(List csFilesFound) "PackOptions.cs", "PasswordEncryptionKeyLocation.cs", "ToLoggingDatabaseDataLoadEventListener.cs", - "ExecuteCommandIdentifyCHIInCatalogue", - "ExecuteCommandRedactCHIsFromCatalogue", - "CHIColumnFinder" + }); //allowed because it's default arguments for CLI prohibitedStrings.Add("TEST_"); From 5a8eeeb5b6f48f391f31418e8c195b259578114b Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 09:00:40 +0000 Subject: [PATCH 13/64] add file extention --- .../ClassFileEvaluation/ExplicitDatabaseNameChecker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs index 10c7d1f4e0..1cbce16a2d 100644 --- a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs +++ b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs @@ -29,9 +29,9 @@ public static void FindProblems(List csFilesFound) "PluginPackagerProgramOptions.cs", //allwed because it's a suggestion to the user about command line arguments "DocumentationCrossExaminationTest.cs", //allowed because its basically a list of comments that are allowed despite not appearing in the codebase "ResearchDataManagementPlatformOptions.cs", //allowed because it's an Example, - "ExecuteCommandIdentifyCHIInCatalogue", //uses the RDMP_ALL allow list - "ExecuteCommandRedactCHIsFromCatalogue", //uses the RDMP_ALL allow list - "CHIColumnFinder" //uses the RDMP_ALL allow list + "ExecuteCommandIdentifyCHIInCatalogue.cs", //uses the RDMP_ALL allow list + "ExecuteCommandRedactCHIsFromCatalogue.cs", //uses the RDMP_ALL allow list + "CHIColumnFinder.cs" //uses the RDMP_ALL allow list }; From f0466e39edecf35fada9f7e843e934df3c58a6f5 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 09:22:11 +0000 Subject: [PATCH 14/64] add ignore strings --- .../ClassFileEvaluation/ExplicitDatabaseNameChecker.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs index 1cbce16a2d..3111f29fbc 100644 --- a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs +++ b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs @@ -28,10 +28,8 @@ public static void FindProblems(List csFilesFound) "ChoosePlatformDatabasesUI.Designer.cs", //allowed because it is a suggestion to user about what prefix to use "PluginPackagerProgramOptions.cs", //allwed because it's a suggestion to the user about command line arguments "DocumentationCrossExaminationTest.cs", //allowed because its basically a list of comments that are allowed despite not appearing in the codebase - "ResearchDataManagementPlatformOptions.cs", //allowed because it's an Example, - "ExecuteCommandIdentifyCHIInCatalogue.cs", //uses the RDMP_ALL allow list - "ExecuteCommandRedactCHIsFromCatalogue.cs", //uses the RDMP_ALL allow list - "CHIColumnFinder.cs" //uses the RDMP_ALL allow list + "ResearchDataManagementPlatformOptions.cs" //allowed because it's an Example, + }; @@ -46,6 +44,9 @@ public static void FindProblems(List csFilesFound) "PackOptions.cs", "PasswordEncryptionKeyLocation.cs", "ToLoggingDatabaseDataLoadEventListener.cs", + "ExecuteCommandIdentifyCHIInCatalogue.cs", //uses the RDMP_ALL allow list + "ExecuteCommandRedactCHIsFromCatalogue.cs", //uses the RDMP_ALL allow list + "CHIColumnFinder.cs" //uses the RDMP_ALL allow list }); //allowed because it's default arguments for CLI From 6cf51737e2fd2492a4ffd569462e21074ed78b55 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 09:37:50 +0000 Subject: [PATCH 15/64] change visability --- Rdmp.Core/Curation/Data/RedactedCHI.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index dbf097fbfc..1590a983db 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -87,17 +87,11 @@ public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI } public RedactedCHI() { } - internal RedactedCHI(ICatalogueRepository repository, DbDataReader r) + public RedactedCHI(ICatalogueRepository repository, DbDataReader r) : base(repository, r) { PotentialCHI = r["PotentialChi"].ToString(); CHIContext = r["CHIContext"].ToString(); CHILocation = r["CHILocation"].ToString(); - // Name = r["Name"].ToString(); - // Folder = r["Folder"].ToString(); - // if (r["DigitalObjectIdentifier"] != DBNull.Value) - // DigitalObjectIdentifier = r["DigitalObjectIdentifier"].ToString(); - // if (r["Source"] != DBNull.Value) - // Source = r["Source"].ToString(); } } \ No newline at end of file From b6a2848817615a984b0da368b27d025e3b917e9b Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 09:43:16 +0000 Subject: [PATCH 16/64] add missing headers --- .../AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs | 9 +++++++-- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 6 +++++- .../AtomicCommands/ExecuteCommandRevertRedactedCHI.cs | 7 ++++++- .../Components/Runtime/ExecuteCHIRedactionStage.cs | 7 ++++++- .../DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs | 7 ++++++- .../ExecuteCommandRedactCHIsInCatalogue.cs | 7 ++++++- 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs index 31d4b4b46e..baf82ef945 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs @@ -1,10 +1,15 @@ -using Rdmp.Core.Curation.Data; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using Rdmp.Core.Curation.Data; namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandConfirmRedactedCHI : BasicCommandExecution, IAtomicCommand { - RedactedCHI _redactedCHI; + readonly RedactedCHI _redactedCHI; public ExecuteCommandConfirmRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("redactionto confirm")]RedactedCHI redaction): base(activator) { _redactedCHI = redaction; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index cabe83895a..8d2f71e465 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -1,4 +1,8 @@ - +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index bab329a100..5c970f3dce 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -1,4 +1,9 @@ -using Microsoft.Data.SqlClient; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using Microsoft.Data.SqlClient; using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.DataAccess; using System; diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 07ab275c81..254e414cfe 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -1,4 +1,9 @@ -using FAnsi.Discovery; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using FAnsi.Discovery; using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; diff --git a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs index 64679b8406..61972200c2 100644 --- a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs +++ b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs @@ -1,4 +1,9 @@ -using FAnsi.Discovery; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using FAnsi.Discovery; using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.DataLoad; using Rdmp.Core.DataLoad.Engine.Job; diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs index f973e5cf7b..1e47d600ff 100644 --- a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs @@ -1,4 +1,9 @@ -using Rdmp.Core.Curation.Data; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using Rdmp.Core.Curation.Data; using Rdmp.UI.ItemActivation; using Rdmp.UI.SimpleDialogs; using System; From 1967a732d000e9121c50cbe12b31c1c730454a01 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 09:53:39 +0000 Subject: [PATCH 17/64] update unit test --- Tests.Common/UnitTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests.Common/UnitTests.cs b/Tests.Common/UnitTests.cs index e15ac1b6e4..f8b45732ce 100644 --- a/Tests.Common/UnitTests.cs +++ b/Tests.Common/UnitTests.cs @@ -123,6 +123,10 @@ public static T WhenIHaveA(MemoryDataExportRepository repository) where T : D return (T)(object)Save(new Catalogue(repository, "Mycata")); + if (typeof(T) == typeof(RedactedCHI)) + return (T)(object)Save(new RedactedCHI(repository,"123456789","123456789","[test].[db]")); + + if (typeof(T) == typeof(ExtendedProperty)) return (T)(object)new ExtendedProperty(repository, Save(new Catalogue(repository, "Mycata")), "TestProp", 0); From 4a7ad537c93a39082d2dc3dc8653cdd7f0e5ced6 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 10:09:46 +0000 Subject: [PATCH 18/64] add creation db code --- Rdmp.Core/Curation/Data/RedactedCHI.cs | 13 ------------ .../CreateCatalogue.sql | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 1590a983db..33368fa2ae 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -65,19 +65,6 @@ public string CHILocation set => SetField(ref _chiLocation, value); } - //[Unique] - //public string DigitalObjectIdentifier - //{ - // get => _digitalObjectIdentifier; - // set => SetField(ref _digitalObjectIdentifier, value); - //} - - //public string Source - //{ - // get => _source; - // set => SetField(ref _source, value); - //} - public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI, string chiContext,string chiLocation) { catalogueRepository.InsertAndHydrate(this, new Dictionary diff --git a/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql b/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql index 95818e0d03..fbb56873ca 100644 --- a/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql +++ b/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql @@ -893,6 +893,26 @@ CREATE TABLE [dbo].[TableInfo]( )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +/****** Object: RedactedCHI [dbo].[RedactedCHI] Script Date: 29/11/2023 14:16:46 ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +SET ANSI_PADDING ON +GO +GO +CREATE TABLE [dbo].RedactedCHI( + [ID] [int] IDENTITY(1,1) NOT NULL, + [PotentialCHI] [varchar](500) NOT NULL, + [CHIContext][nvarchar](50) NOT NULL, + CHILocation[nvarchar](500) NOT NULL, + CONSTRAINT [PK_RedactedCHI] PRIMARY KEY CLUSTERED +( + [ID] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] + + GO SET ANSI_PADDING OFF GO From 63e2e2e5b119ba51ad7ff825a752791e9aa55fa9 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 10:18:55 +0000 Subject: [PATCH 19/64] try no params --- Tests.Common/UnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests.Common/UnitTests.cs b/Tests.Common/UnitTests.cs index f8b45732ce..5271a2070d 100644 --- a/Tests.Common/UnitTests.cs +++ b/Tests.Common/UnitTests.cs @@ -124,7 +124,7 @@ public static T WhenIHaveA(MemoryDataExportRepository repository) where T : D if (typeof(T) == typeof(RedactedCHI)) - return (T)(object)Save(new RedactedCHI(repository,"123456789","123456789","[test].[db]")); + return (T)(object)Save(new RedactedCHI());//repository,"123456789","123456789","[test].[db]" if (typeof(T) == typeof(ExtendedProperty)) From 8ccde9d8115f103ddc7000b0de972760170c147d Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 29 Nov 2023 11:32:21 +0000 Subject: [PATCH 20/64] update test --- Tests.Common/UnitTests.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Tests.Common/UnitTests.cs b/Tests.Common/UnitTests.cs index 5271a2070d..bed939872e 100644 --- a/Tests.Common/UnitTests.cs +++ b/Tests.Common/UnitTests.cs @@ -59,7 +59,8 @@ public class UnitTests "ExtractableCohort", "DQEGraphAnnotation", "Evaluation", - "WindowLayout" + "WindowLayout", + "RedactedCHI" }); @@ -122,11 +123,6 @@ public static T WhenIHaveA(MemoryDataExportRepository repository) where T : D if (typeof(T) == typeof(Catalogue)) return (T)(object)Save(new Catalogue(repository, "Mycata")); - - if (typeof(T) == typeof(RedactedCHI)) - return (T)(object)Save(new RedactedCHI());//repository,"123456789","123456789","[test].[db]" - - if (typeof(T) == typeof(ExtendedProperty)) return (T)(object)new ExtendedProperty(repository, Save(new Catalogue(repository, "Mycata")), "TestProp", 0); From bdea723fc90602a5b7232e39b7daa2e4d56193f6 Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 30 Nov 2023 09:42:27 +0000 Subject: [PATCH 21/64] temp --- .../ExecuteCommandRedactCHIsFromCatalogue.cs | 26 ++++----- Rdmp.Core/Curation/Data/IRedactedCHI.cs | 9 +-- Rdmp.Core/Curation/Data/RedactedCHI.cs | 56 ++++++++++--------- .../Runtime/ExecuteCHIRedactionStage.cs | 15 ++++- .../up/078_add_chi_redaction.sql | 6 +- .../RedactChisInCatalogueDialog.cs | 4 +- 6 files changed, 68 insertions(+), 48 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index 1affa409a6..e22a99176b 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -50,19 +50,19 @@ private void handleFoundCHI(string foundChi, string table, string column, string { Console.WriteLine("Found CHI!"); redactionCount++; - var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi,columnValue,20),$"{table}.{column}"); - rc.SaveToDatabase(); - var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); - //TODO can be smarted about how we wrote tothe db, can share a connection etc - var sql = $"UPDATE {table} SET {column}='{redactedValue}' where {column}='{columnValue}'"; - var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - var conn = server.GetConnection(); - conn.Open(); - using (var cmd = server.GetCommand(sql, conn)) - { - cmd.ExecuteNonQuery(); - conn.Close(); - } + //var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi,columnValue,20),$"{table}.{column}"); + //rc.SaveToDatabase(); + //var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + ////TODO can be smarted about how we wrote tothe db, can share a connection etc + //var sql = $"UPDATE {table} SET {column}='{redactedValue}' where {column}='{columnValue}'"; + //var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); + //var conn = server.GetConnection(); + //conn.Open(); + //using (var cmd = server.GetCommand(sql, conn)) + //{ + // cmd.ExecuteNonQuery(); + // conn.Close(); + //} } diff --git a/Rdmp.Core/Curation/Data/IRedactedCHI.cs b/Rdmp.Core/Curation/Data/IRedactedCHI.cs index cb76840707..300f2fc0ca 100644 --- a/Rdmp.Core/Curation/Data/IRedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/IRedactedCHI.cs @@ -19,13 +19,14 @@ namespace Rdmp.Core.Curation.Data; /// /// This class stores the redacted CHIs that are found during data load of via catalogue mutilation /// -public interface IRedactedCHI :IMapsDirectlyToDatabaseTable +public interface IRedactedCHI : IMapsDirectlyToDatabaseTable { ICatalogueRepository CatalogueRepository { get; } string PotentialCHI { get; } - string CHIContext{ get; } - - string CHILocation { get; } + int ReplacementIndex { get; } + string TableName { get; } + string PKValue { get; } + string ColumnName { get; } } diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 33368fa2ae..5ff91dc680 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -28,21 +28,10 @@ namespace Rdmp.Core.Curation.Data; public class RedactedCHI : DatabaseEntity, IRedactedCHI { private string _potentialCHI; - private string _chiContext; - private string _chiLocation; - //string _name; - //string _digitalObjectIdentifier; - //string _source; - //private string _folder = FolderHelper.Root; - - ///// - //[DoNotImportDescriptions] - //[UsefulProperty] - //public string Folder - //{ - // get => _folder; - // set => SetField(ref _folder, FolderHelper.Adjust(value)); - //} + private int _replacementIndex; + private string _table; + private string _pkValue; + private string _columnName; [NotNull] public string PotentialCHI @@ -52,24 +41,38 @@ public string PotentialCHI } [NotNull] - public string CHIContext + public int ReplacementIndex + { + get => _replacementIndex; + set => SetField(ref _replacementIndex, value); + } + + [NotNull] + public string TableName { - get => _chiContext; - set => SetField(ref _chiContext, value); + get => _table; + set => SetField(ref _table, value); } [NotNull] - public string CHILocation + public string PKValue { - get => _chiLocation; - set => SetField(ref _chiLocation, value); + get => _pkValue; + set => SetField(ref _pkValue, value); } - public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI, string chiContext,string chiLocation) + [NotNull] + public string ColumnName + { + get => _columnName; + set => SetField(ref _columnName, value); + } + + public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI, int replacementIndex, string table, string pkValue,string columnName) { catalogueRepository.InsertAndHydrate(this, new Dictionary { - {"potentialCHI", potentialCHI },{"CHIContext",chiContext},{"CHILocation", chiLocation} + {"potentialCHI", potentialCHI },{"replacementIndex",replacementIndex},{"tableName",table},{"PKValue",pkValue},{"cColumnName",columnName} }); } @@ -78,7 +81,10 @@ public RedactedCHI(ICatalogueRepository repository, DbDataReader r) : base(repository, r) { PotentialCHI = r["PotentialChi"].ToString(); - CHIContext = r["CHIContext"].ToString(); - CHILocation = r["CHILocation"].ToString(); + ReplacementIndex = int.Parse(r["ReplacementIndex"].ToString()); + TableName = r["TableName"].ToString(); + PKValue = r["PKValue"].ToString(); + ColumnName = r["ColumnName"].ToString(); + } } \ No newline at end of file diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 254e414cfe..e8c497899a 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -71,9 +71,20 @@ private void RedactCHIs(ITableInfo tableInfo) { if (_redact) { - var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, row[col].ToString(), 20), $"{tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "")}.[{col.ColumnName}]"); //todo make sure this matches + var replacementIdex = row[col].ToString().IndexOf(foundChi); + var foundTable = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); + var pk = tbl.DiscoverColumns().Where(col => col.IsPrimaryKey).First(); + var pkValue = ""; + if(pk is not null) + { + + //pkValue = row[pk.] + } + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, foundTable, pkValue, col.ColumnName); + //var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, row[col].ToString(), 20), $"{tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "")}.[{col.ColumnName}]"); //todo make sure this matches rc.SaveToDatabase(); - row[col] = row[col].ToString().Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + var redactionString = "REDACTED###"; + row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0,Math.Min(foundChi.Length,redactionString.Length))); } else { diff --git a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql index 8cbec26e41..8a23018219 100644 --- a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql +++ b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql @@ -7,8 +7,10 @@ begin CREATE TABLE [dbo].RedactedCHI( [ID] [int] IDENTITY(1,1) NOT NULL, [PotentialCHI] [varchar](500) NOT NULL, - [CHIContext][nvarchar](50) NOT NULL, - CHILocation[nvarchar](500) NOT NULL, + ReplacementIndex[int] NOT NULL, + TableName[nvarchar](500) NOT NULL, + PKValue[nvarchar](500) NOT NULL, + ColumnName[nvarchar](500) NOT NULL, CONSTRAINT [PK_RedactedCHI] PRIMARY KEY CLUSTERED ( [ID] ASC diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index d60237c197..1710fb92fb 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -46,8 +46,8 @@ private void Redact(int rowIndex) var column = result.ItemArray[2].ToString(); var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; - var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, columnValue, name); - rc.SaveToDatabase(); + //var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, columnValue, name); + //rc.SaveToDatabase(); result.Delete(); _results.AcceptChanges(); dgResults.DataSource = _results; From 15e90dbf1940fca39c72c7ff3d9d472135017030 Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 30 Nov 2023 13:09:57 +0000 Subject: [PATCH 22/64] update redacted chi --- .../ResearchDataManagementPlatform.csproj | 5 +- .../ExecuteCommandRevertRedactedCHI.cs | 44 +++++++-------- Rdmp.Core/Curation/Data/RedactedCHI.cs | 2 +- .../Runtime/ExecuteCHIRedactionStage.cs | 28 +++++++--- .../Mutilators/CHIRedactionMutilator.cs | 4 +- .../ViewRedactedCHIsInCatalogueDialog.cs | 56 +++++++++---------- 6 files changed, 75 insertions(+), 64 deletions(-) diff --git a/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj b/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj index e78dd26e59..1e755f74aa 100644 --- a/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj +++ b/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj @@ -35,8 +35,8 @@ - - + + RDMPMainForm.cs @@ -97,6 +97,7 @@ WindowManagement\Licenses\LIBRARYLICENSES + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 5c970f3dce..8835dc01ec 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -29,27 +29,27 @@ public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsIn public override void Execute() { base.Execute(); - var splitidx = _redactedCHI.CHILocation.LastIndexOf('.'); - var table = _redactedCHI.CHILocation[..splitidx]; - var column = _redactedCHI.CHILocation[(splitidx + 1)..]; - var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == _redactedCHI.CHILocation).First(); - var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; - var findSlq = $"select {column} from {table} where {column} like '%REDACTED_CHI_{_redactedCHI.ID}%';"; - var existingResultsDT = new DataTable(); - using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) - { - con.Open(); - var da = new SqlDataAdapter(new SqlCommand(findSlq, con)); - da.Fill(existingResultsDT); - if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) - { - var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); - var newContext = currentContext.Replace($"REDACTED_CHI_{_redactedCHI.ID}", _redactedCHI.PotentialCHI); - var updateSQL = $"update {table} set {column}='{newContext}' where {column} = '{currentContext}'"; - var updateCmd = new SqlCommand(updateSQL, con); - updateCmd.ExecuteNonQuery(); - } - _redactedCHI.DeleteInDatabase(); - } + //var splitidx = _redactedCHI.CHILocation.LastIndexOf('.'); + //var table = _redactedCHI.CHILocation[..splitidx]; + //var column = _redactedCHI.CHILocation[(splitidx + 1)..]; + //var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == _redactedCHI.CHILocation).First(); + //var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; + //var findSlq = $"select {column} from {table} where {column} like '%REDACTED_CHI_{_redactedCHI.ID}%';"; + //var existingResultsDT = new DataTable(); + //using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + //{ + // con.Open(); + // var da = new SqlDataAdapter(new SqlCommand(findSlq, con)); + // da.Fill(existingResultsDT); + // if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) + // { + // var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); + // var newContext = currentContext.Replace($"REDACTED_CHI_{_redactedCHI.ID}", _redactedCHI.PotentialCHI); + // var updateSQL = $"update {table} set {column}='{newContext}' where {column} = '{currentContext}'"; + // var updateCmd = new SqlCommand(updateSQL, con); + // updateCmd.ExecuteNonQuery(); + // } + // _redactedCHI.DeleteInDatabase(); + //} } } diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 5ff91dc680..6c5355fecb 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -72,7 +72,7 @@ public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI { catalogueRepository.InsertAndHydrate(this, new Dictionary { - {"potentialCHI", potentialCHI },{"replacementIndex",replacementIndex},{"tableName",table},{"PKValue",pkValue},{"cColumnName",columnName} + {"potentialCHI", potentialCHI },{"replacementIndex",replacementIndex},{"tableName",table},{"PKValue",pkValue},{"columnName",columnName} }); } diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index e8c497899a..1b818f2fd8 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -72,19 +72,29 @@ private void RedactCHIs(ITableInfo tableInfo) if (_redact) { var replacementIdex = row[col].ToString().IndexOf(foundChi); - var foundTable = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); - var pk = tbl.DiscoverColumns().Where(col => col.IsPrimaryKey).First(); - var pkValue = ""; - if(pk is not null) + var foundTable = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "").Split("..")[1].Replace("[", "").Replace("]", ""); + var catalogue = _job.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(catalogue => catalogue.Name == foundTable).First(); + var pkValue = "fake"; + if (catalogue != null) { - - //pkValue = row[pk.] + //this can probably be tidied up + var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); + if (pkColumnInfo != null) + { + var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); + var arrayNames = (from DataColumn x + in dt.Columns.Cast() + select x.ColumnName).ToList(); + var index = arrayNames.IndexOf(pkName); + pkValue = row[index].ToString(); + } } - var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, foundTable, pkValue, col.ColumnName); - //var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, row[col].ToString(), 20), $"{tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "")}.[{col.ColumnName}]"); //todo make sure this matches + var ft = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); + ft = ft.Replace("..", ".[dbo]."); + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex,ft, pkValue, $"[{col.ColumnName}]"); rc.SaveToDatabase(); var redactionString = "REDACTED###"; - row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0,Math.Min(foundChi.Length,redactionString.Length))); + row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0, Math.Min(foundChi.Length, redactionString.Length))); } else { diff --git a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs index 61972200c2..92d9e2441b 100644 --- a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs +++ b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs @@ -26,7 +26,7 @@ internal class CHIRedactionMutilator : IPluginMutilateDataTables private DiscoveredDatabase _db; private LoadStage _loadStage; - private readonly Dictionary> _allowLists = null; + private readonly Dictionary> _allowLists = new(); @@ -40,7 +40,7 @@ internal class CHIRedactionMutilator : IPluginMutilateDataTables public bool Redact { get; set; } = true; public void Check(ICheckNotifier notifier) { - if (AllowListFileLocation != null) + if (!string.IsNullOrWhiteSpace(AllowListFileLocation)) { var allowListFileContent = File.ReadAllText(AllowListFileLocation); var deserializer = new DeserializerBuilder().Build(); diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index dced3a1e3c..e693f4a482 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -38,21 +38,21 @@ public ViewRedactedCHIsInCatalogueDialog(IBasicActivateItems activator, ICatalog private void RevertButtonClick(int itemIndex) { - var result = _results.Rows[itemIndex]; - var potentialCHI = result.ItemArray[0].ToString(); - var context = result.ItemArray[1].ToString(); - var column = result.ItemArray[3].ToString(); - var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); - if (redactedChi is not null) - { - var cmd = new ExecuteCommandRevertRedactedCHI(_activator, redactedChi); - cmd.Execute(); - result.Delete(); - _results.AcceptChanges(); - dtResults.DataSource = _results; - dtResults.Columns[5].Visible = false;//todo this isn't quite right - - } + //var result = _results.Rows[itemIndex]; + //var potentialCHI = result.ItemArray[0].ToString(); + //var context = result.ItemArray[1].ToString(); + //var column = result.ItemArray[3].ToString(); + //var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); + //if (redactedChi is not null) + //{ + // var cmd = new ExecuteCommandRevertRedactedCHI(_activator, redactedChi); + // cmd.Execute(); + // result.Delete(); + // _results.AcceptChanges(); + // dtResults.DataSource = _results; + // dtResults.Columns[5].Visible = false;//todo this isn't quite right + + //} } private void ConfirmButtonClick(int itemIndex) @@ -61,17 +61,17 @@ private void ConfirmButtonClick(int itemIndex) var potentialCHI = result.ItemArray[0].ToString(); var context = result.ItemArray[1].ToString(); var column = result.ItemArray[3].ToString(); - var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); - if (redactedChi is not null) - { - var cmd = new ExecuteCommandConfirmRedactedCHI(_activator, redactedChi); - cmd.Execute(); - result.Delete(); - _results.AcceptChanges(); - dtResults.DataSource = _results; - dtResults.Columns[5].Visible = false;//todo this isn't quite right - - } + //var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); + //if (redactedChi is not null) + //{ + // var cmd = new ExecuteCommandConfirmRedactedCHI(_activator, redactedChi); + // cmd.Execute(); + // result.Delete(); + // _results.AcceptChanges(); + // dtResults.DataSource = _results; + // dtResults.Columns[5].Visible = false;//todo this isn't quite right + + //} } private void handleClick(object sender, DataGridViewCellEventArgs e) @@ -120,7 +120,7 @@ private void FindChis() lblLoading.Visible = _isLoading; dtResults.Visible = !_isLoading; List columns = _catalogue.CatalogueItems.Select(ci => ci.ColumnInfo).Select(ci => ci.Name).ToList(); - List redactedChis = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(rc => columns.Contains(rc.CHILocation)).ToList();// + List redactedChis = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(rc => columns.Contains($"{rc.TableName}.[{rc.ColumnName}]")).ToList();// @@ -131,7 +131,7 @@ private void FindChis() dt.Columns.Add(new DataColumn("_hiddenFullLocation", typeof(string))); foreach (var rc in redactedChis) { - dt.Rows.Add(new object[] { rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation), rc.CHILocation }); + //dt.Rows.Add(new object[] { rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation), rc.CHILocation }); } dtResults.DataSource = dt; dtResults.Columns[3].Visible = false; From b910bac2aeb2898410490a0f5b202c57072fbbf7 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 4 Dec 2023 11:59:42 +0000 Subject: [PATCH 23/64] updated data load --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 38 +++++++++++++++---- Rdmp.Core/Curation/Data/RedactedCHI.cs | 13 ++++++- .../Runtime/ExecuteCHIRedactionStage.cs | 6 ++- .../CreateCatalogue.sql | 7 +++- .../up/078_add_chi_redaction.sql | 1 + Rdmp.UI/Menus/CatalogueMenu.cs | 12 +++++- .../RedactChisInCatalogueDialog.cs | 7 +++- .../ViewRedactedCHIsInCatalogueDialog.cs | 6 ++- 8 files changed, 70 insertions(+), 20 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 8d2f71e465..32508965fb 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -3,6 +3,7 @@ // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using Org.BouncyCastle.Asn1.Crmf; using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; @@ -33,7 +34,7 @@ public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [Dema _catalouge = catalogue; _activator = activator; _bailOutEarly = bailOutEarly; - if(!string.IsNullOrWhiteSpace(allowListLocation)) + if (!string.IsNullOrWhiteSpace(allowListLocation)) { var allowListFileContent = File.ReadAllText(allowListLocation); var deserializer = new DeserializerBuilder().Build(); @@ -54,17 +55,19 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 - private void handleFoundCHI(string foundChi,string contextValue, string columnName) + private void handleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue) { - if(foundChis.Rows.Count == 0) + if (foundChis.Rows.Count == 0) { //init foundChis.Columns.Add("Potential CHI"); foundChis.Columns.Add("Context"); foundChis.Columns.Add("Source Column Name"); + foundChis.Columns.Add("PK Value"); + foundChis.Columns.Add("replacementIndex"); } - var shrunkContext = WrapCHIInContext(foundChi,contextValue); - foundChis.Rows.Add(foundChi, shrunkContext, columnName); + var shrunkContext = WrapCHIInContext(foundChi, contextValue); + foundChis.Rows.Add(foundChi, shrunkContext, columnName, pkValue, contextValue.IndexOf(foundChi)); } public DataTable foundChis = new(); @@ -104,21 +107,40 @@ public override void Execute() var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); if (!string.IsNullOrWhiteSpace(potentialCHI)) { - handleFoundCHI(potentialCHI, value, item.Name); + var pkValue = getPKValue(row, dt); + handleFoundCHI(potentialCHI, value, item.Name, pkValue); if (_bailOutEarly) { break; } } - + } } Console.WriteLine($"Found {foundChis.Rows.Count} CHIs in the {_catalouge.Name} Catalogue."); - foreach(DataRow row in foundChis.Rows) + foreach (DataRow row in foundChis.Rows) { Console.WriteLine($"{row["potential CHI"]} | {row["Context"]} | {row["Source Column Name"]}"); } } + + private string getPKValue(DataRow row, DataTable dt) + { + var pkColumnInfo = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); + if (pkColumnInfo != null) + { + var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); + var arrayNames = (from DataColumn x + in dt.Columns.Cast() + select x.ColumnName).ToList(); + var index = arrayNames.IndexOf(pkName); + if (index != -1) + { + return row[index].ToString(); + } + } + return "Error: Unknown PK"; + } } diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 6c5355fecb..d1e413d70d 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -31,6 +31,7 @@ public class RedactedCHI : DatabaseEntity, IRedactedCHI private int _replacementIndex; private string _table; private string _pkValue; + private string _pkColumnName; private string _columnName; [NotNull] @@ -61,6 +62,13 @@ public string PKValue set => SetField(ref _pkValue, value); } + [NotNull] + public string PKColumnName + { + get => _pkColumnName; + set => SetField(ref _pkColumnName, value); + } + [NotNull] public string ColumnName { @@ -68,11 +76,11 @@ public string ColumnName set => SetField(ref _columnName, value); } - public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI, int replacementIndex, string table, string pkValue,string columnName) + public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI, int replacementIndex, string table, string pkValue, string pkColumnName, string columnName) { catalogueRepository.InsertAndHydrate(this, new Dictionary { - {"potentialCHI", potentialCHI },{"replacementIndex",replacementIndex},{"tableName",table},{"PKValue",pkValue},{"columnName",columnName} + {"potentialCHI", potentialCHI },{"replacementIndex",replacementIndex},{"tableName",table},{"PKValue",pkValue},{"pkColumnName",pkColumnName},{"columnName",columnName} }); } @@ -84,6 +92,7 @@ public RedactedCHI(ICatalogueRepository repository, DbDataReader r) ReplacementIndex = int.Parse(r["ReplacementIndex"].ToString()); TableName = r["TableName"].ToString(); PKValue = r["PKValue"].ToString(); + PKValue = r["PKColumnName"].ToString(); ColumnName = r["ColumnName"].ToString(); } diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 1b818f2fd8..538f92219e 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -75,10 +75,12 @@ private void RedactCHIs(ITableInfo tableInfo) var foundTable = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "").Split("..")[1].Replace("[", "").Replace("]", ""); var catalogue = _job.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(catalogue => catalogue.Name == foundTable).First(); var pkValue = "fake"; + var pkColumnName = "Unknown"; if (catalogue != null) { //this can probably be tidied up var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); + pkColumnName = pkColumnInfo.Name; if (pkColumnInfo != null) { var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); @@ -91,9 +93,9 @@ in dt.Columns.Cast() } var ft = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); ft = ft.Replace("..", ".[dbo]."); - var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex,ft, pkValue, $"[{col.ColumnName}]"); + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex,ft, pkValue, pkColumnName.Split(".").Last(),$"[{col.ColumnName}]"); rc.SaveToDatabase(); - var redactionString = "REDACTED###"; + var redactionString = "##########"; row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0, Math.Min(foundChi.Length, redactionString.Length))); } else diff --git a/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql b/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql index fbb56873ca..b961285c3d 100644 --- a/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql +++ b/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql @@ -904,8 +904,11 @@ GO CREATE TABLE [dbo].RedactedCHI( [ID] [int] IDENTITY(1,1) NOT NULL, [PotentialCHI] [varchar](500) NOT NULL, - [CHIContext][nvarchar](50) NOT NULL, - CHILocation[nvarchar](500) NOT NULL, + ReplacementIndex[int] NOT NULL, + TableName[nvarchar](500) NOT NULL, + PKValue[nvarchar](500) NOT NULL, + PKColumnName[nvarchar](500) NOT NULL, + ColumnName[nvarchar](500) NOT NULL, CONSTRAINT [PK_RedactedCHI] PRIMARY KEY CLUSTERED ( [ID] ASC diff --git a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql index 8a23018219..1ebcac4e99 100644 --- a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql +++ b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql @@ -10,6 +10,7 @@ CREATE TABLE [dbo].RedactedCHI( ReplacementIndex[int] NOT NULL, TableName[nvarchar](500) NOT NULL, PKValue[nvarchar](500) NOT NULL, + PKColumnName[nvarchar](500) NOT NULL, ColumnName[nvarchar](500) NOT NULL, CONSTRAINT [PK_RedactedCHI] PRIMARY KEY CLUSTERED ( diff --git a/Rdmp.UI/Menus/CatalogueMenu.cs b/Rdmp.UI/Menus/CatalogueMenu.cs index 4c01d43206..755dd61b1b 100644 --- a/Rdmp.UI/Menus/CatalogueMenu.cs +++ b/Rdmp.UI/Menus/CatalogueMenu.cs @@ -24,8 +24,16 @@ internal class CatalogueMenu : RDMPContextMenuStrip public CatalogueMenu(RDMPContextMenuStripArgs args, Catalogue catalogue) : base(args, catalogue) { var isApiCall = catalogue.IsApiCall(); - Add(new ExecuteCommandRedactCHIsInCatalogue(_activator, catalogue)); - Add(new ExecuteCommandViewRedactedCHIsInCatalogue(_activator,catalogue)); + Add(new ExecuteCommandRedactCHIsInCatalogue(_activator, catalogue) + { + OverrideCommandName = "Find && Redact CHIs in Catalogue", + Weight = -86.9f + }); + Add(new ExecuteCommandViewRedactedCHIsInCatalogue(_activator, catalogue) + { + OverrideCommandName = "View Redacted CHIs in Catalogue", + Weight = -86.9f + }); Add(new ExecuteCommandGenerateMetadataReport(_activator, catalogue) { diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 1710fb92fb..5287414134 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -6,6 +6,7 @@ using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.UI.ItemActivation; +using Rdmp.UI.MainFormUITabs; using System; using System.Collections.Generic; using System.ComponentModel; @@ -46,8 +47,10 @@ private void Redact(int rowIndex) var column = result.ItemArray[2].ToString(); var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; - //var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, columnValue, name); - //rc.SaveToDatabase(); + var pkValue = result.ItemArray[3].ToString(); + var replacementIdex = int.Parse(result.ItemArray[4].ToString()); + var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, name.Replace($".[{column}]",""), pkValue,"TODO", $"[{column}]"); + rc.SaveToDatabase(); result.Delete(); _results.AcceptChanges(); dgResults.DataSource = _results; diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index e693f4a482..d3c34b73a1 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -120,7 +120,7 @@ private void FindChis() lblLoading.Visible = _isLoading; dtResults.Visible = !_isLoading; List columns = _catalogue.CatalogueItems.Select(ci => ci.ColumnInfo).Select(ci => ci.Name).ToList(); - List redactedChis = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(rc => columns.Contains($"{rc.TableName}.[{rc.ColumnName}]")).ToList();// + List redactedChis = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(rc => columns.Contains($"{rc.TableName}.{rc.ColumnName}")).ToList();// @@ -128,10 +128,12 @@ private void FindChis() dt.Columns.Add(new DataColumn("Potental CHI", typeof(string))); dt.Columns.Add(new DataColumn("Context", typeof(string))); dt.Columns.Add(new DataColumn("Column", typeof(string))); - dt.Columns.Add(new DataColumn("_hiddenFullLocation", typeof(string))); + //dt.Columns.Add(new DataColumn("_hiddenFullLocation", typeof(string))); foreach (var rc in redactedChis) { //dt.Rows.Add(new object[] { rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation), rc.CHILocation }); + var context = "TODO"; + dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName }); } dtResults.DataSource = dt; dtResults.Columns[3].Visible = false; From e9cce516eda0b9436f80990a134f117bd1abe77a Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 4 Dec 2023 13:22:50 +0000 Subject: [PATCH 24/64] working restore --- .../ExecuteCommandRevertRedactedCHI.cs | 43 +++++++++-------- Rdmp.Core/Curation/Data/RedactedCHI.cs | 2 +- .../Runtime/ExecuteCHIRedactionStage.cs | 3 +- .../ViewRedactedCHIsInCatalogueDialog.cs | 47 ++++++++++--------- 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 8835dc01ec..89c90c90a8 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -29,27 +29,26 @@ public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsIn public override void Execute() { base.Execute(); - //var splitidx = _redactedCHI.CHILocation.LastIndexOf('.'); - //var table = _redactedCHI.CHILocation[..splitidx]; - //var column = _redactedCHI.CHILocation[(splitidx + 1)..]; - //var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == _redactedCHI.CHILocation).First(); - //var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; - //var findSlq = $"select {column} from {table} where {column} like '%REDACTED_CHI_{_redactedCHI.ID}%';"; - //var existingResultsDT = new DataTable(); - //using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) - //{ - // con.Open(); - // var da = new SqlDataAdapter(new SqlCommand(findSlq, con)); - // da.Fill(existingResultsDT); - // if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) - // { - // var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); - // var newContext = currentContext.Replace($"REDACTED_CHI_{_redactedCHI.ID}", _redactedCHI.PotentialCHI); - // var updateSQL = $"update {table} set {column}='{newContext}' where {column} = '{currentContext}'"; - // var updateCmd = new SqlCommand(updateSQL, con); - // updateCmd.ExecuteNonQuery(); - // } - // _redactedCHI.DeleteInDatabase(); - //} + var redactedString = new string('#', _redactedCHI.PotentialCHI.Length); + var fetchSQL = $"select {_redactedCHI.ColumnName} from {_redactedCHI.TableName} where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and charindex('{redactedString}',{_redactedCHI.ColumnName},{_redactedCHI.ReplacementIndex}) =1"; + var existingResultsDT = new DataTable(); + var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{_redactedCHI.TableName}.{_redactedCHI.ColumnName}").First(); + var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; + using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + { + con.Open(); + var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); + da.Fill(existingResultsDT); + if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) + { + var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); + var newContext = currentContext.Replace(redactedString, _redactedCHI.PotentialCHI); + var updateSQL = $"update {_redactedCHI.TableName} set {_redactedCHI.ColumnName}='{newContext}' where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and {_redactedCHI.ColumnName}='{currentContext}'"; + var updateCmd = new SqlCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + _redactedCHI.DeleteInDatabase(); + + } } } diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index d1e413d70d..6983c1091f 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -91,8 +91,8 @@ public RedactedCHI(ICatalogueRepository repository, DbDataReader r) PotentialCHI = r["PotentialChi"].ToString(); ReplacementIndex = int.Parse(r["ReplacementIndex"].ToString()); TableName = r["TableName"].ToString(); + PKColumnName = r["PKColumnName"].ToString(); PKValue = r["PKValue"].ToString(); - PKValue = r["PKColumnName"].ToString(); ColumnName = r["ColumnName"].ToString(); } diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 538f92219e..3f29aac6ad 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -91,7 +91,8 @@ in dt.Columns.Cast() pkValue = row[index].ToString(); } } - var ft = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); + //var ft = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); + var ft = tbl.GetFullyQualifiedName().Replace("_RAW", ""); ft = ft.Replace("..", ".[dbo]."); var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex,ft, pkValue, pkColumnName.Split(".").Last(),$"[{col.ColumnName}]"); rc.SaveToDatabase(); diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index d3c34b73a1..041e456f50 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -38,30 +38,32 @@ public ViewRedactedCHIsInCatalogueDialog(IBasicActivateItems activator, ICatalog private void RevertButtonClick(int itemIndex) { - //var result = _results.Rows[itemIndex]; - //var potentialCHI = result.ItemArray[0].ToString(); - //var context = result.ItemArray[1].ToString(); - //var column = result.ItemArray[3].ToString(); - //var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); - //if (redactedChi is not null) - //{ - // var cmd = new ExecuteCommandRevertRedactedCHI(_activator, redactedChi); - // cmd.Execute(); - // result.Delete(); - // _results.AcceptChanges(); - // dtResults.DataSource = _results; - // dtResults.Columns[5].Visible = false;//todo this isn't quite right + var result = _results.Rows[itemIndex]; + var potentialCHI = result.ItemArray[0].ToString(); + var column = result.ItemArray[2].ToString(); + var table = result.ItemArray[3].ToString(); + var pkColumn = result.ItemArray[4].ToString(); + var pkValue = result.ItemArray[5].ToString(); + var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.ColumnName == column && rc.PKColumnName == pkColumn && rc.PKValue == pkValue && rc.TableName == table).First(); + if (redactedChi is not null) + { + var cmd = new ExecuteCommandRevertRedactedCHI(_activator, redactedChi); + cmd.Execute(); + result.Delete(); + _results.AcceptChanges(); + dtResults.DataSource = _results; + dtResults.Columns[5].Visible = false;//todo this isn't quite right - //} + } } private void ConfirmButtonClick(int itemIndex) { - var result = _results.Rows[itemIndex]; - var potentialCHI = result.ItemArray[0].ToString(); - var context = result.ItemArray[1].ToString(); - var column = result.ItemArray[3].ToString(); - //var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.CHIContext == context && rc.CHILocation == column).First(); + //var result = _results.Rows[itemIndex]; + //var potentialCHI = result.ItemArray[0].ToString(); + //var context = result.ItemArray[1].ToString(); + //var column = result.ItemArray[3].ToString(); + //var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && column == rc.ColumnName).First(); //if (redactedChi is not null) //{ // var cmd = new ExecuteCommandConfirmRedactedCHI(_activator, redactedChi); @@ -128,12 +130,13 @@ private void FindChis() dt.Columns.Add(new DataColumn("Potental CHI", typeof(string))); dt.Columns.Add(new DataColumn("Context", typeof(string))); dt.Columns.Add(new DataColumn("Column", typeof(string))); - //dt.Columns.Add(new DataColumn("_hiddenFullLocation", typeof(string))); + dt.Columns.Add(new DataColumn("Table", typeof(string))); + dt.Columns.Add(new DataColumn("_pkColumn", typeof(string))); + dt.Columns.Add(new DataColumn("_pkValue", typeof(string))); foreach (var rc in redactedChis) { - //dt.Rows.Add(new object[] { rc.PotentialCHI, rc.CHIContext, locationToColumn(rc.CHILocation), rc.CHILocation }); var context = "TODO"; - dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName }); + dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName, rc.TableName,rc.PKColumnName,rc.PKValue }); } dtResults.DataSource = dt; dtResults.Columns[3].Visible = false; From 135288fd31ed564f9093b815d71c26a8170e3158 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 4 Dec 2023 13:52:36 +0000 Subject: [PATCH 25/64] working identify --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 10 +++++- .../ViewRedactedCHIsInCatalogueDialog.cs | 36 ++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 32508965fb..526f344e04 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -8,6 +8,7 @@ using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.Core.DataFlowPipeline; +using Rdmp.Core.DataViewing; using Rdmp.Core.ReusableLibraryCode.DataAccess; using System; using System.Collections.Generic; @@ -79,6 +80,9 @@ public override void Execute() columnAllowList.AddRange(_extractionSpecificAllowances); if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); + + + foreach (var item in _catalouge.CatalogueItems) { if (columnAllowList.Contains(item.Name)) continue; @@ -91,7 +95,8 @@ public override void Execute() int idxOfLastSplit = column.LastIndexOf('.'); var columnName = column[(idxOfLastSplit + 1)..]; var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - var sql = $"SELECT {columnName} from {column[..idxOfLastSplit]}"; + var pkColumn = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First().Name.Split(".").Last(); + var sql = $"SELECT {columnName},{pkColumn} from {column[..idxOfLastSplit]}"; var dt = new DataTable(); dt.BeginLoadData(); using (var cmd = server.GetCommand(sql, server.GetConnection())) @@ -128,6 +133,9 @@ public override void Execute() private string getPKValue(DataRow row, DataTable dt) { + //todo doesn't work with multitable catalogues + + var pkColumnInfo = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); if (pkColumnInfo != null) { diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 041e456f50..443734c1fa 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -59,21 +59,23 @@ private void RevertButtonClick(int itemIndex) private void ConfirmButtonClick(int itemIndex) { - //var result = _results.Rows[itemIndex]; - //var potentialCHI = result.ItemArray[0].ToString(); - //var context = result.ItemArray[1].ToString(); - //var column = result.ItemArray[3].ToString(); - //var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && column == rc.ColumnName).First(); - //if (redactedChi is not null) - //{ - // var cmd = new ExecuteCommandConfirmRedactedCHI(_activator, redactedChi); - // cmd.Execute(); - // result.Delete(); - // _results.AcceptChanges(); - // dtResults.DataSource = _results; - // dtResults.Columns[5].Visible = false;//todo this isn't quite right - - //} + var result = _results.Rows[itemIndex]; + var potentialCHI = result.ItemArray[0].ToString(); + var column = result.ItemArray[2].ToString(); + var table = result.ItemArray[3].ToString(); + var pkColumn = result.ItemArray[4].ToString(); + var pkValue = result.ItemArray[5].ToString(); + var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.ColumnName == column && rc.PKColumnName == pkColumn && rc.PKValue == pkValue && rc.TableName == table).First(); + if (redactedChi is not null) + { + var cmd = new ExecuteCommandConfirmRedactedCHI(_activator, redactedChi); + cmd.Execute(); + result.Delete(); + _results.AcceptChanges(); + dtResults.DataSource = _results; + dtResults.Columns[5].Visible = false;//todo this isn't quite right + + } } private void handleClick(object sender, DataGridViewCellEventArgs e) @@ -93,7 +95,7 @@ private void RevertAll(object sender, EventArgs e) { if (_activator.YesNo("Do you want to revert all these redactions?", "Revert All")) { - foreach (var rIndex in Enumerable.Range(0,_results.Rows.Count)) + foreach (var rIndex in Enumerable.Range(0, _results.Rows.Count)) { RevertButtonClick(rIndex); } @@ -136,7 +138,7 @@ private void FindChis() foreach (var rc in redactedChis) { var context = "TODO"; - dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName, rc.TableName,rc.PKColumnName,rc.PKValue }); + dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName, rc.TableName, rc.PKColumnName, rc.PKValue }); } dtResults.DataSource = dt; dtResults.Columns[3].Visible = false; From 085781624563e37b7ed662c475dff055c1216ad7 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 13:31:33 +0000 Subject: [PATCH 26/64] fix mutilator --- .../Runtime/ExecuteCHIRedactionStage.cs | 24 ++++++++++++------- .../Mutilators/CHIRedactionMutilator.cs | 14 ++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 3f29aac6ad..d828626227 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -25,7 +26,7 @@ internal class ExecuteCHIRedactionStage private readonly IDataLoadJob _job; private readonly DiscoveredDatabase _db; private readonly LoadStage _loadStage; - private Dictionary> _allowList; + private Dictionary> _allowLists = new(); private bool _redact; public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadStage loadStage) @@ -35,12 +36,12 @@ public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadSta _loadStage = loadStage; } - public ExitCodeType Execute(bool redact, Dictionary> allowLists = null) + public ExitCodeType Execute(bool redact, Dictionary> allowLists = null) { if (_loadStage != LoadStage.AdjustRaw && _loadStage != LoadStage.AdjustStaging) throw new NotSupportedException("This mutilator can only run in AdjustRaw or AdjustStaging"); - _allowList = allowLists; + _allowLists = allowLists; _redact = redact; foreach (var tableInfo in _job.RegularTablesToLoad) { @@ -61,9 +62,15 @@ private void RedactCHIs(ITableInfo tableInfo) _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, $"About to run {GetType()} mutilation on table {tbl}")); var dt = tbl.GetDataTable(); + List _allowList = new(); + if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) + _allowList.AddRange(_extractionSpecificAllowances); + var y = tbl.ToString(); + if (_allowLists.TryGetValue(tbl.ToString(), out var _catalogueSpecificAllowances)) + _allowList.AddRange(_catalogueSpecificAllowances.ToList()); foreach (DataColumn col in dt.Columns) { - if (_allowList.ContainsKey(col.ColumnName)) continue; + if (_allowList.Contains(col.ColumnName)) continue; foreach (DataRow row in dt.Rows) { var foundChi = CHIColumnFinder.GetPotentialCHI(row[col].ToString()); @@ -79,7 +86,7 @@ private void RedactCHIs(ITableInfo tableInfo) if (catalogue != null) { //this can probably be tidied up - var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); + var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey && x.Name.Contains(foundTable)).First(); //there may be more, but we just need one pkColumnName = pkColumnInfo.Name; if (pkColumnInfo != null) { @@ -91,10 +98,9 @@ in dt.Columns.Cast() pkValue = row[index].ToString(); } } - //var ft = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), ""); - var ft = tbl.GetFullyQualifiedName().Replace("_RAW", ""); - ft = ft.Replace("..", ".[dbo]."); - var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex,ft, pkValue, pkColumnName.Split(".").Last(),$"[{col.ColumnName}]"); + var ft = tbl.GetFullyQualifiedName().Replace("_RAW", ""); //TODO + //ft = ft.Replace("..", ".[dbo]."); + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); rc.SaveToDatabase(); var redactionString = "##########"; row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0, Math.Min(foundChi.Length, redactionString.Length))); diff --git a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs index 92d9e2441b..d653af07e8 100644 --- a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs +++ b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs @@ -13,6 +13,7 @@ using Rdmp.Core.ReusableLibraryCode.Progress; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -26,7 +27,7 @@ internal class CHIRedactionMutilator : IPluginMutilateDataTables private DiscoveredDatabase _db; private LoadStage _loadStage; - private readonly Dictionary> _allowLists = new(); + private Dictionary> _allowLists = new(); @@ -39,15 +40,21 @@ internal class CHIRedactionMutilator : IPluginMutilateDataTables [DemandsInitialization("Automatically redact found CHIS. Otherwise, a warning is raised", DemandType = DemandType.Unspecified)] public bool Redact { get; set; } = true; public void Check(ICheckNotifier notifier) + { + PopulateAllowList(); + } + + private void PopulateAllowList() { if (!string.IsNullOrWhiteSpace(AllowListFileLocation)) { var allowListFileContent = File.ReadAllText(AllowListFileLocation); + _allowLists = new(); var deserializer = new DeserializerBuilder().Build(); var yamlObject = deserializer.Deserialize>>(allowListFileContent); - foreach (var (cat, columns) in yamlObject) + foreach (var item in yamlObject) { - _allowLists.Add(cat, columns); + _allowLists.Add(item.Key, item.Value); } } } @@ -64,6 +71,7 @@ public void LoadCompletedSoDispose(ExitCodeType exitCode, IDataLoadEventListener public ExitCodeType Mutilate(IDataLoadJob job) { + PopulateAllowList(); var redactor = new ExecuteCHIRedactionStage(job, _db, _loadStage); return redactor.Execute(Redact, _allowLists); } From 3d0a74176bfa47e1ed063db2e2a34c07611a12ef Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 13:46:53 +0000 Subject: [PATCH 27/64] working redaction --- .../AtomicCommands/ExecuteCommandRevertRedactedCHI.cs | 8 ++++---- .../Components/Runtime/ExecuteCHIRedactionStage.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 89c90c90a8..6c2fee13b7 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -20,7 +20,7 @@ public class ExecuteCommandRevertRedactedCHI : BasicCommandExecution, IAtomicCom RedactedCHI _redactedCHI; IBasicActivateItems _activator; - public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHIto Revert")] RedactedCHI redaction) : base(activator) + public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHI to Revert")] RedactedCHI redaction) : base(activator) { _redactedCHI = redaction; _activator = activator; @@ -30,7 +30,7 @@ public override void Execute() { base.Execute(); var redactedString = new string('#', _redactedCHI.PotentialCHI.Length); - var fetchSQL = $"select {_redactedCHI.ColumnName} from {_redactedCHI.TableName} where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and charindex('{redactedString}',{_redactedCHI.ColumnName},{_redactedCHI.ReplacementIndex}) =1"; + var fetchSQL = $"select {_redactedCHI.ColumnName} from {_redactedCHI.TableName} where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and charindex('{redactedString}',{_redactedCHI.ColumnName},{_redactedCHI.ReplacementIndex}) >0"; var existingResultsDT = new DataTable(); var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{_redactedCHI.TableName}.{_redactedCHI.ColumnName}").First(); var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; @@ -45,9 +45,9 @@ public override void Execute() var newContext = currentContext.Replace(redactedString, _redactedCHI.PotentialCHI); var updateSQL = $"update {_redactedCHI.TableName} set {_redactedCHI.ColumnName}='{newContext}' where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and {_redactedCHI.ColumnName}='{currentContext}'"; var updateCmd = new SqlCommand(updateSQL, con); - updateCmd.ExecuteNonQuery(); + //updateCmd.ExecuteNonQuery(); } - _redactedCHI.DeleteInDatabase(); + //_redactedCHI.DeleteInDatabase(); } } diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index d828626227..21b3cce88b 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -99,7 +99,7 @@ in dt.Columns.Cast() } } var ft = tbl.GetFullyQualifiedName().Replace("_RAW", ""); //TODO - //ft = ft.Replace("..", ".[dbo]."); + ft = ft.Replace("..", ".[dbo]."); var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); rc.SaveToDatabase(); var redactionString = "##########"; From 99f660e4473a46d48790bea820c21dfc6be109d6 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 13:47:18 +0000 Subject: [PATCH 28/64] readd command execution --- .../AtomicCommands/ExecuteCommandRevertRedactedCHI.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 6c2fee13b7..06c0d6046d 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -45,9 +45,9 @@ public override void Execute() var newContext = currentContext.Replace(redactedString, _redactedCHI.PotentialCHI); var updateSQL = $"update {_redactedCHI.TableName} set {_redactedCHI.ColumnName}='{newContext}' where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and {_redactedCHI.ColumnName}='{currentContext}'"; var updateCmd = new SqlCommand(updateSQL, con); - //updateCmd.ExecuteNonQuery(); + updateCmd.ExecuteNonQuery(); } - //_redactedCHI.DeleteInDatabase(); + _redactedCHI.DeleteInDatabase(); } } From 02fc8c2e12d489ec0a064413047d989946c3819c Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 14:14:10 +0000 Subject: [PATCH 29/64] working redaction --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 14 ++++----- .../RedactChisInCatalogueDialog.cs | 30 +++++++++++++++++-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 526f344e04..39dd88c476 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -56,7 +56,7 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 - private void handleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue) + private void handleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) { if (foundChis.Rows.Count == 0) { @@ -65,10 +65,11 @@ private void handleFoundCHI(string foundChi, string contextValue, string columnN foundChis.Columns.Add("Context"); foundChis.Columns.Add("Source Column Name"); foundChis.Columns.Add("PK Value"); + foundChis.Columns.Add("PK Column"); foundChis.Columns.Add("replacementIndex"); } var shrunkContext = WrapCHIInContext(foundChi, contextValue); - foundChis.Rows.Add(foundChi, shrunkContext, columnName, pkValue, contextValue.IndexOf(foundChi)); + foundChis.Rows.Add(foundChi, shrunkContext, columnName, pkValue, pkColumn, contextValue.IndexOf(foundChi)); } public DataTable foundChis = new(); @@ -112,8 +113,9 @@ public override void Execute() var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); if (!string.IsNullOrWhiteSpace(potentialCHI)) { - var pkValue = getPKValue(row, dt); - handleFoundCHI(potentialCHI, value, item.Name, pkValue); + var pkColumnInfo = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); + var pkValue = getPKValue(pkColumnInfo, row, dt); + handleFoundCHI(potentialCHI, value, item.Name, pkValue, pkColumnInfo.Name); if (_bailOutEarly) { break; @@ -131,12 +133,10 @@ public override void Execute() } } - private string getPKValue(DataRow row, DataTable dt) + private string getPKValue(ColumnInfo pkColumnInfo, DataRow row, DataTable dt) { //todo doesn't work with multitable catalogues - - var pkColumnInfo = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); if (pkColumnInfo != null) { var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 5287414134..ec77f3a8ad 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -3,8 +3,10 @@ // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . +using Microsoft.Data.SqlClient; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; +using Rdmp.Core.ReusableLibraryCode.DataAccess; using Rdmp.UI.ItemActivation; using Rdmp.UI.MainFormUITabs; using System; @@ -48,9 +50,33 @@ private void Redact(int rowIndex) var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; var pkValue = result.ItemArray[3].ToString(); - var replacementIdex = int.Parse(result.ItemArray[4].ToString()); - var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, name.Replace($".[{column}]",""), pkValue,"TODO", $"[{column}]"); + var pkColumn = result.ItemArray[4].ToString(); + var replacementIdex = int.Parse(result.ItemArray[5].ToString()); + var table = name.Replace($".[{column}]", ""); + var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue,"TODO", $"[{column}]"); rc.SaveToDatabase(); + + var redactedString = new string('#', foundChi.Length); + var fetchSQL = $"select {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; + var existingResultsDT = new DataTable(); + var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{table}.[{column}]").First(); + var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; + using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + { + con.Open(); + var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); + da.Fill(existingResultsDT); + if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) + { + var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); + var newContext = currentContext.Replace(foundChi, redactedString); + var updateSQL = $"update {table} set {column}='{newContext}' where {pkColumn} = '{pkValue}' and {column}='{currentContext}'"; + var updateCmd = new SqlCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + } + + result.Delete(); _results.AcceptChanges(); dgResults.DataSource = _results; From e9383134e088d25e9441ade36df6322f2ef7635b Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 15:09:33 +0000 Subject: [PATCH 30/64] start of the dqe --- .../Secondary/DoesNotContainCHIConstraint.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs diff --git a/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs b/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs new file mode 100644 index 0000000000..d30495e3a6 --- /dev/null +++ b/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs @@ -0,0 +1,46 @@ +using Rdmp.Core.DataFlowPipeline; +using Rdmp.Core.MapsDirectlyToDatabaseTable; +using Rdmp.Core.ReusableLibraryCode.Checks; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.Core.Validation.Constraints.Secondary; + +public class DoesNotContainCHIConstraint : SecondaryConstraint, ICheckable +{ + private readonly IRepository _repository; + + public DoesNotContainCHIConstraint() { + _repository = Validator.LocatorForXMLDeserialization.CatalogueRepository; + } + + + public void Check(ICheckNotifier notifier) + { + } + + public override string GetHumanReadableDescriptionOfValidation() + { + return "TODO"; + } + + public override void RenameColumn(string originalName, string newName) + { + } + + public override ValidationFailure Validate(object value, object[] otherColumns, string[] otherColumnNames) + { + if (value == null || value == DBNull.Value) + return null; + + if (string.IsNullOrWhiteSpace(value.ToString())) + return null; + var potentialCHI = CHIColumnFinder.GetPotentialCHI(value.ToString()); + if(string.IsNullOrWhiteSpace(potentialCHI)) return null; + return new ValidationFailure($"Potential CHI {potentialCHI} was found.",this); + } +} From 4528aee2fb0f80c6e66e8e59d3fb033818ba5b61 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 15:26:55 +0000 Subject: [PATCH 31/64] document files --- .../Secondary/DoesNotContainCHIConstraint.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs b/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs index d30495e3a6..73cfa59b1f 100644 --- a/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs +++ b/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs @@ -1,4 +1,9 @@ -using Rdmp.Core.DataFlowPipeline; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . +using Rdmp.Core.DataFlowPipeline; using Rdmp.Core.MapsDirectlyToDatabaseTable; using Rdmp.Core.ReusableLibraryCode.Checks; using System; @@ -9,7 +14,9 @@ using System.Threading.Tasks; namespace Rdmp.Core.Validation.Constraints.Secondary; - +/// +/// TODO +/// public class DoesNotContainCHIConstraint : SecondaryConstraint, ICheckable { private readonly IRepository _repository; From efa7312cbb9cd5830a65d1f1a2c351a55e051686 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 5 Dec 2023 15:45:16 +0000 Subject: [PATCH 32/64] add rdmp all ignore --- .../ClassFileEvaluation/ExplicitDatabaseNameChecker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs index 3111f29fbc..4fcfe7d979 100644 --- a/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs +++ b/Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/ExplicitDatabaseNameChecker.cs @@ -46,7 +46,8 @@ public static void FindProblems(List csFilesFound) "ToLoggingDatabaseDataLoadEventListener.cs", "ExecuteCommandIdentifyCHIInCatalogue.cs", //uses the RDMP_ALL allow list "ExecuteCommandRedactCHIsFromCatalogue.cs", //uses the RDMP_ALL allow list - "CHIColumnFinder.cs" //uses the RDMP_ALL allow list + "CHIColumnFinder.cs", //uses the RDMP_ALL allow list + "ExecuteCHIRedactionStage.cs" //Users the RDMP_ALL allow list }); //allowed because it's default arguments for CLI From da567f72f9ae41a65633f65f0a4bce4eb5de8a06 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 11 Dec 2023 12:12:32 +0000 Subject: [PATCH 33/64] attempt to fix sql --- .../runAfterCreateDatabase/CreateCatalogue.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql b/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql index d3c5919160..3584db35e7 100644 --- a/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql +++ b/Rdmp.Core/Databases/CatalogueDatabase/runAfterCreateDatabase/CreateCatalogue.sql @@ -936,10 +936,15 @@ CREATE TABLE [dbo].RedactedCHI( PKColumnName[nvarchar](500) NOT NULL, ColumnName[nvarchar](500) NOT NULL, CONSTRAINT [PK_RedactedCHI] PRIMARY KEY CLUSTERED + ( + [ID] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] GO SET ANSI_PADDING OFF GO + ALTER TABLE [dbo].[ANOTable] ADD CONSTRAINT [DF_ANOTable_SoftwareVersion] DEFAULT ([dbo].[GetSoftwareVersion]()) FOR [SoftwareVersion] GO ALTER TABLE [dbo].[AggregateConfiguration] ADD CONSTRAINT [DF_AggregateConfiguration_dtCreated] DEFAULT (getdate()) FOR [dtCreated] From 6a7e64004e4f7658da494b147272ed71978b93d7 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 11 Dec 2023 14:22:18 +0000 Subject: [PATCH 34/64] dont redact pks --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 3 +++ ...ExecuteCommandPasteClipboardAsNewCatalogueItems.cs | 4 ++-- .../Components/Runtime/ExecuteCHIRedactionStage.cs | 11 +++++++---- .../ExecuteCommandLinkCatalogueToDatasetUI.cs | 4 ++-- Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs | 7 +++++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 39dd88c476..d53f1a7e75 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -30,6 +30,7 @@ public class ExecuteCommandIdentifyCHIInCatalogue : BasicCommandExecution, IAtom private bool _bailOutEarly; private readonly Dictionary> _allowLists = new(); + //TODO don;t show PKs public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, bool bailOutEarly = false, string allowListLocation = null) : base(activator) { _catalouge = catalogue; @@ -58,6 +59,8 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 private void handleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) { + if (pkColumn.Split(".").Last().Replace("[", "").Replace("]", "") == columnName.Replace("[", "").Replace("]", "")) return; //don't redact PKs, it gets messy + //^TODO check this works and doesn;t need to munge the []^ if (foundChis.Rows.Count == 0) { //init diff --git a/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs b/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs index f79ffa7576..0e49bcc293 100644 --- a/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs +++ b/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs @@ -45,8 +45,8 @@ public ExecuteCommandPasteClipboardAsNewCatalogueItems(IBasicActivateItems activ _clipboardContentGetter = clipboardContentGetter; } - public override Image GetImage(IIconProvider iconProvider) => - iconProvider.GetImage(RDMPConcept.Clipboard, OverlayKind.Import); + //public override Image GetImage(IIconProvider iconProvider) => + // iconProvider.GetImage(RDMPConcept.Clipboard, OverlayKind.Import); public override void Execute() { diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 21b3cce88b..9a74b857e6 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -100,10 +100,13 @@ in dt.Columns.Cast() } var ft = tbl.GetFullyQualifiedName().Replace("_RAW", ""); //TODO ft = ft.Replace("..", ".[dbo]."); - var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); - rc.SaveToDatabase(); - var redactionString = "##########"; - row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0, Math.Min(foundChi.Length, redactionString.Length))); + if (pkColumnName.Split(".").Last().Replace("[","").Replace("]","") != $"{col.ColumnName}") + { + var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); + rc.SaveToDatabase(); + var redactionString = "##########"; + row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0, Math.Min(foundChi.Length, redactionString.Length))); + } } else { diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs index d0b99eb69d..00db3270ce 100644 --- a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs @@ -49,6 +49,6 @@ public override void Execute() cmd.Execute(); } - public override Image GetImage([NotNull] IIconProvider iconProvider) => - iconProvider.GetImage(RDMPConcept.Dataset, OverlayKind.Link); + //public override Image GetImage([NotNull] IIconProvider iconProvider) => + // iconProvider.GetImage(RDMPConcept.Dataset, OverlayKind.Link); } \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index ec77f3a8ad..397789a2ae 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -53,11 +53,11 @@ private void Redact(int rowIndex) var pkColumn = result.ItemArray[4].ToString(); var replacementIdex = int.Parse(result.ItemArray[5].ToString()); var table = name.Replace($".[{column}]", ""); - var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue,"TODO", $"[{column}]"); + var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue,pkColumn, $"[{column}]"); rc.SaveToDatabase(); var redactedString = new string('#', foundChi.Length); - var fetchSQL = $"select {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; + var fetchSQL = $"select TOP 1 {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; var existingResultsDT = new DataTable(); var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{table}.[{column}]").First(); var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; @@ -93,6 +93,9 @@ private void handleClick(object sender, DataGridViewCellEventArgs e) private void ShowResults() { + if(_results.Rows.Count ==0) { + //TODO show a 'no results found' message + } dgResults.DataSource = _results; DataGridViewButtonColumn confirmColumn = new DataGridViewButtonColumn(); confirmColumn.Text = "Redact"; From 1245852e58c942df5a7fd81f2574d9b909cbc307 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 11 Dec 2023 16:28:32 +0000 Subject: [PATCH 35/64] add early return --- Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 397789a2ae..11d0431e82 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -93,8 +93,9 @@ private void handleClick(object sender, DataGridViewCellEventArgs e) private void ShowResults() { - if(_results.Rows.Count ==0) { + if(_results.Rows.Count ==0) { //TODO show a 'no results found' message + return; } dgResults.DataSource = _results; DataGridViewButtonColumn confirmColumn = new DataGridViewButtonColumn(); From c950bae34a06183c365659f496e0b0f998aa2237 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 12 Dec 2023 09:23:48 +0000 Subject: [PATCH 36/64] working confirm all --- .../ExecuteCommandRedactCHIsFromCatalogue.cs | 153 +++++++++++------- .../ViewRedactedCHIsInCatalogueDialog.cs | 4 +- 2 files changed, 100 insertions(+), 57 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index e22a99176b..7b0e3caa45 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -20,88 +20,131 @@ using TB.ComponentModel; using FAnsi.Discovery.TableCreation; using Rdmp.Core.DataFlowPipeline; +using Microsoft.Data.SqlClient; namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandRedactCHIsFromCatalogue : BasicCommandExecution, IAtomicCommand { - private ICatalogue _catalouge; + private ICatalogue _catalogue; private IBasicActivateItems _activator; private readonly Dictionary> _allowLists = new(); public int redactionCount = 0; + private string _allowListLocation = ""; public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, string allowListLocation = null) : base(activator) { - _catalouge = catalogue; + _catalogue = catalogue; _activator = activator; - if (!string.IsNullOrWhiteSpace(allowListLocation)) - { - var allowListFileContent = File.ReadAllText(allowListLocation); - var deserializer = new DeserializerBuilder().Build(); - var yamlObject = deserializer.Deserialize>>(allowListFileContent); - foreach (var (cat, columns) in yamlObject) - { - _allowLists.Add(cat, columns); - } - } - } - private void handleFoundCHI(string foundChi, string table, string column, string columnValue) - { - Console.WriteLine("Found CHI!"); - redactionCount++; - //var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi,columnValue,20),$"{table}.{column}"); - //rc.SaveToDatabase(); - //var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); - ////TODO can be smarted about how we wrote tothe db, can share a connection etc - //var sql = $"UPDATE {table} SET {column}='{redactedValue}' where {column}='{columnValue}'"; - //var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - //var conn = server.GetConnection(); - //conn.Open(); - //using (var cmd = server.GetCommand(sql, conn)) + _allowListLocation = allowListLocation; + //if (!string.IsNullOrWhiteSpace(allowListLocation)) //{ - // cmd.ExecuteNonQuery(); - // conn.Close(); + // var allowListFileContent = File.ReadAllText(allowListLocation); + // var deserializer = new DeserializerBuilder().Build(); + // var yamlObject = deserializer.Deserialize>>(allowListFileContent); + // foreach (var (cat, columns) in yamlObject) + // { + // _allowLists.Add(cat, columns); + // } //} } + //private void handleFoundCHI(string foundChi, string table, string column, string columnValue) + //{ + // //check for PK + // Console.WriteLine("Found CHI!"); + // redactionCount++; + // var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, columnValue, 20), $"{table}.{column}"); + // rc.SaveToDatabase(); + // var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); + // //TODO can be smarted about how we wrote tothe db, can share a connection etc + // var sql = $"UPDATE {table} SET {column}='{redactedValue}' where {column}='{columnValue}'"; + // var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); + // var conn = server.GetConnection(); + // conn.Open(); + // using (var cmd = server.GetCommand(sql, conn)) + // { + // cmd.ExecuteNonQuery(); + // conn.Close(); + // } + //} public override void Execute() { base.Execute(); - List columnAllowList = new(); - if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) - columnAllowList.AddRange(_extractionSpecificAllowances); - if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) - columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); - foreach (var item in _catalouge.CatalogueItems) + var cmd = new ExecuteCommandIdentifyCHIInCatalogue(_activator, _catalogue, false, _allowListLocation); + cmd.Execute(); + DataTable results = cmd.foundChis; + foreach(DataRow row in results.Rows) { - if (columnAllowList.Contains(item.Name)) continue; + redactionCount++; + var result = row; + var foundChi = result.ItemArray[0].ToString(); + var columnValue = result.ItemArray[1].ToString(); + var column = result.ItemArray[2].ToString(); + var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); + var name = catalogueItem.ColumnInfo.Name; + var pkValue = result.ItemArray[3].ToString(); + var pkColumn = result.ItemArray[4].ToString(); + var replacementIdex = int.Parse(result.ItemArray[5].ToString()); + var table = name.Replace($".[{column}]", ""); + var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue, pkColumn, $"[{column}]"); + rc.SaveToDatabase(); - var column = item.ColumnInfo.Name; - int idxOfLastSplit = column.LastIndexOf('.'); - string table = column[..idxOfLastSplit]; - var columnName = column[(idxOfLastSplit + 1)..]; - var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - var sql = $"SELECT {columnName} from {table}"; - var dt = new DataTable(); - dt.BeginLoadData(); - using (var cmd = server.GetCommand(sql, server.GetConnection())) + var redactedString = new string('#', foundChi.Length); + var fetchSQL = $"select TOP 1 {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; + var existingResultsDT = new DataTable(); + var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{table}.[{column}]").First(); + var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; + using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) { - using var da = server.GetDataAdapter(cmd); - da.Fill(dt); - } - dt.EndLoadData(); - foreach (DataRow row in dt.Rows) - { - - var value = row[dt.Columns[0].ColumnName].ToString(); - var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); - if (!string.IsNullOrWhiteSpace(potentialCHI)) + con.Open(); + var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); + da.Fill(existingResultsDT); + if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) { - handleFoundCHI(potentialCHI, table,columnName,value); + var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); + var newContext = currentContext.Replace(foundChi, redactedString); + var updateSQL = $"update {table} set {column}='{newContext}' where {pkColumn} = '{pkValue}' and {column}='{currentContext}'"; + var updateCmd = new SqlCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); } } } + //List columnAllowList = new(); + //if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) + // columnAllowList.AddRange(_extractionSpecificAllowances); + //if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) + // columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); + //foreach (var item in _catalouge.CatalogueItems) + //{ + // if (columnAllowList.Contains(item.Name)) continue; + + // var column = item.ColumnInfo.Name; + // int idxOfLastSplit = column.LastIndexOf('.'); + // string table = column[..idxOfLastSplit]; + // var columnName = column[(idxOfLastSplit + 1)..]; + // var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); + // var sql = $"SELECT {columnName} from {table}"; + // var dt = new DataTable(); + // dt.BeginLoadData(); + // using (var cmd = server.GetCommand(sql, server.GetConnection())) + // { + // using var da = server.GetDataAdapter(cmd); + // da.Fill(dt); + // } + // dt.EndLoadData(); + // foreach (DataRow row in dt.Rows) + // { + + // var value = row[dt.Columns[0].ColumnName].ToString(); + // var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); + // if (!string.IsNullOrWhiteSpace(potentialCHI)) + // { + // handleFoundCHI(potentialCHI, table,columnName,value); + // } + // } + //} } } diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 443734c1fa..2685c7f2c5 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -105,9 +105,9 @@ private void ConfirmAll(object sender, EventArgs e) { if (_activator.YesNo("Do you want to confirm all these redactions?", "Confirm All")) { - foreach (var rIndex in Enumerable.Range(0, _results.Rows.Count)) + foreach (var rIndex in Enumerable.Range(0,_results.Rows.Count)) { - ConfirmButtonClick(rIndex); + ConfirmButtonClick(0); } } } From 9916d7f4089147f42d1abf5803516df3246066dc Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 12 Dec 2023 09:46:47 +0000 Subject: [PATCH 37/64] tidy up ui --- .../RedactChisInCatalogueDialog.cs | 8 ++++++ .../ViewRedactedCHIsInCatalogueDialog.cs | 26 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 11d0431e82..2c6ad56d21 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -30,6 +30,7 @@ public partial class RedactChisInCatalogueDialog : Form private IActivateItems _activator; private ICatalogue _catalogue; private DataTable _results; + private bool _firstTime = true; public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogue) { @@ -107,6 +108,13 @@ private void ShowResults() { dgResults.Columns.Insert(3, confirmColumn); } + if (_firstTime) + { + dgResults.Columns[4].Visible = false; + dgResults.Columns[5].Visible = false; + dgResults.Columns[6].Visible = false; + _firstTime = false; + } dgResults.Visible = true; lbResults.Visible = false; } diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 2685c7f2c5..e9a4b96196 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -21,6 +21,7 @@ namespace Rdmp.UI.SimpleDialogs public partial class ViewRedactedCHIsInCatalogueDialog : Form { private bool _isLoading = true; + private bool _firstTime = true; private IBasicActivateItems _activator; private ICatalogue _catalogue; private DataTable _results; @@ -52,7 +53,10 @@ private void RevertButtonClick(int itemIndex) result.Delete(); _results.AcceptChanges(); dtResults.DataSource = _results; - dtResults.Columns[5].Visible = false;//todo this isn't quite right + //dtResults.Columns[5].Visible = false; + //dtResults.Columns[5].Visible = false; + //dtResults.Columns[5].Visible = false; + //dtResults.Columns[5].Visible = false;//todo this isn't quite right } } @@ -73,7 +77,9 @@ private void ConfirmButtonClick(int itemIndex) result.Delete(); _results.AcceptChanges(); dtResults.DataSource = _results; - dtResults.Columns[5].Visible = false;//todo this isn't quite right + //dtResults.Columns[5].Visible = false; + + //dtResults.Columns[5].Visible = false;//todo this isn't quite right } } @@ -112,12 +118,6 @@ private void ConfirmAll(object sender, EventArgs e) } } - private string locationToColumn(string location) - { - var lastIdx = location.LastIndexOf('.'); - return location[(lastIdx + 1)..]; - } - private void FindChis() { _isLoading = true; @@ -141,8 +141,14 @@ private void FindChis() dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName, rc.TableName, rc.PKColumnName, rc.PKValue }); } dtResults.DataSource = dt; - dtResults.Columns[3].Visible = false; - _results = dt; + if (_firstTime) + { + dtResults.Columns[5].Visible = false; + dtResults.Columns[4].Visible = false; + _firstTime = false; + } + //dtResults.Columns[3].Visible = false; + _results = dt; DataGridViewButtonColumn revertColumn = new DataGridViewButtonColumn(); revertColumn.Text = "Revert"; revertColumn.Name = "Revert"; From d5d9468f9c70d06b56e6cacf35e715ea5ce9ffda Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 12 Dec 2023 10:23:47 +0000 Subject: [PATCH 38/64] update kn issue --- .../AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs | 2 +- Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index 7b0e3caa45..18f9efec51 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -86,9 +86,9 @@ public override void Execute() var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; var pkValue = result.ItemArray[3].ToString(); - var pkColumn = result.ItemArray[4].ToString(); var replacementIdex = int.Parse(result.ItemArray[5].ToString()); var table = name.Replace($".[{column}]", ""); + var pkColumn = result.ItemArray[4].ToString().Replace(table, "").Replace(".", ""); var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue, pkColumn, $"[{column}]"); rc.SaveToDatabase(); diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 2c6ad56d21..e3cd3f66d6 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -51,9 +51,9 @@ private void Redact(int rowIndex) var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; var pkValue = result.ItemArray[3].ToString(); - var pkColumn = result.ItemArray[4].ToString(); var replacementIdex = int.Parse(result.ItemArray[5].ToString()); var table = name.Replace($".[{column}]", ""); + var pkColumn = result.ItemArray[4].ToString().Replace(table,"").Replace(".",""); var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue,pkColumn, $"[{column}]"); rc.SaveToDatabase(); From 61a0bf8d3d56b836f2c1723a008087838b947121 Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 12 Dec 2023 13:57:01 +0000 Subject: [PATCH 39/64] make a start on tests --- .../ExecuteCommandConfirmRedactedCHITests.cs | 36 ++++++++++++ ...ecuteCommandIdentifyCHIInCatalogueTests.cs | 32 +++++++++++ .../ExecuteCommandRevertRedactedCHITests.cs | 39 +++++++++++++ .../ExecuteCHIRedactionStageTests.cs | 56 +++++++++++++++++++ .../ExecuteCommandConfirmRedactedCHI.cs | 2 +- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 1 - Tests.Common/TestDatabases.txt | 2 +- 7 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs create mode 100644 Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs create mode 100644 Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs create mode 100644 Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs new file mode 100644 index 0000000000..054456ee9c --- /dev/null +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.Curation.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tests.Common; + +namespace Rdmp.Core.Tests.CommandExecution; + +internal class ExecuteCommandConfirmRedactedCHITests: DatabaseTests +{ + [Test] + public void ConfirmRedactedCHI_Valid() { + var redactedChisCount = CatalogueRepository.GetAllObjects().Length; + var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]","pkValue","[pkColumn]","[columnName]"); + rCHI.SaveToDatabase(); + + var cmd = new ExecuteCommandConfirmRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); + Assert.DoesNotThrow(()=> cmd.Execute()); + Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); + } + + [Test] + public void ConfirmRedactedCHI_InValid() { + var redactedChisCount = CatalogueRepository.GetAllObjects().Length; + var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); + rCHI.DeleteInDatabase(); + var cmd = new ExecuteCommandConfirmRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); + Assert.DoesNotThrow(() => cmd.Execute()); + Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); + } +} diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs new file mode 100644 index 0000000000..e8d1d3478a --- /dev/null +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs @@ -0,0 +1,32 @@ +using NUnit.Framework; +using Rdmp.Core.Curation.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tests.Common; + +namespace Rdmp.Core.Tests.CommandExecution; + +internal class ExecuteCommandIdentifyCHIInCatalogueTests: DatabaseTests +{ + //bail out early + //allow list + + [Test] + public void IdentifyCHIInCatalogue_Correct() + { + // how do we create a catalogue with data? + //var _catalogue1 = new Catalogue(CatalogueRepository, "Dataset1"); + //var _catalogueItem1 = new CatalogueItem(CatalogueRepository, _catalogue1, "Some Value"); + //var _table1 = new TableInfo(CatalogueRepository, "Table1"); + //var _columnInfo1 = new ColumnInfo(CatalogueRepository, "Some Value", "varchar(10)", _table1); + //_catalogue1.SaveToDatabase(); + //_catalogueItem1.SaveToDatabase(); + //_columnInfo1.SaveToDatabase(); + + + } + +} diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs new file mode 100644 index 0000000000..7292d47e4e --- /dev/null +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -0,0 +1,39 @@ +//using NUnit.Framework; +//using Rdmp.Core.CommandExecution.AtomicCommands; +//using Rdmp.Core.CommandExecution; +//using Rdmp.Core.Curation.Data; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using Tests.Common; + +//namespace Rdmp.Core.Tests.CommandExecution; + +//internal class ExecuteCommandRevertRedactedCHITests: DatabaseTests +//{ +// [Test] +// public void RevertRedactedCHI_Valid() +// { +// var redactedChisCount = CatalogueRepository.GetAllObjects().Length; +// var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); +// rCHI.SaveToDatabase(); +// //needs a columinfo and read data to update + +// var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); +// Assert.DoesNotThrow(() => cmd.Execute()); +// Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); +// } + +// [Test] +// public void RevertRedactedCHI_InValid() +// { +// var redactedChisCount = CatalogueRepository.GetAllObjects().Length; +// var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); +// rCHI.DeleteInDatabase(); +// var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); +// Assert.DoesNotThrow(() => cmd.Execute()); +// Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); +// } +//} diff --git a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs new file mode 100644 index 0000000000..25aed15301 --- /dev/null +++ b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs @@ -0,0 +1,56 @@ +using FAnsi; +using Microsoft.Data.SqlClient; +using NSubstitute; +using NUnit.Framework; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.DataLoad; +using Rdmp.Core.DataLoad.Engine.Job; +using Rdmp.Core.DataLoad.Engine.LoadExecution.Components.Runtime; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tests.Common; + +namespace Rdmp.Core.Tests.DataLoad.Engine.Integration; + +internal class ExecuteCHIRedactionStageTests: DatabaseTests +{ + [TestCase(DatabaseType.MySql)] + [TestCase(DatabaseType.MicrosoftSQLServer)] + public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) + { + + var db = GetCleanedServer(dbType); + using (var con = db.Server.GetConnection()) + { + con.Open(); + + var cmdCreateTable = db.Server.GetCommand( + $"CREATE Table {db.GetRuntimeName()} (SomeValue varchar(10))", con); + cmdCreateTable.ExecuteNonQuery(); + var data = db.Server.GetCommand( + $"insert into {db.GetRuntimeName()} (SomeValue) values(1111111111) ", con); + data.ExecuteNonQuery(); + } + + var job = Substitute.For(); + //job.RegularTablesToLoad.Returns(new List(new[] { db.DiscoverTables })); + var task = new ExecuteCHIRedactionStage(job, db, LoadStage.AdjustRaw); + task.Execute(true); + using (var con = db.Server.GetConnection()) + { + con.Open(); + + var cmdCreateTable = db.Server.GetCommand( + $"select SomeValue {db.GetRuntimeName()}", con); + var dt = new DataTable(); + SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); + da.Fill(dt); + Assert.That("##########", Is.EqualTo(dt.Rows[0][0])); + + } + } +} diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs index baf82ef945..f0c463934c 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs @@ -10,7 +10,7 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandConfirmRedactedCHI : BasicCommandExecution, IAtomicCommand { readonly RedactedCHI _redactedCHI; - public ExecuteCommandConfirmRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("redactionto confirm")]RedactedCHI redaction): base(activator) + public ExecuteCommandConfirmRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHI to confirm")]RedactedCHI redaction): base(activator) { _redactedCHI = redaction; } diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index d53f1a7e75..8ca8c849a7 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -60,7 +60,6 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 private void handleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) { if (pkColumn.Split(".").Last().Replace("[", "").Replace("]", "") == columnName.Replace("[", "").Replace("]", "")) return; //don't redact PKs, it gets messy - //^TODO check this works and doesn;t need to munge the []^ if (foundChis.Rows.Count == 0) { //init diff --git a/Tests.Common/TestDatabases.txt b/Tests.Common/TestDatabases.txt index bc2a95b27d..168e37154c 100644 --- a/Tests.Common/TestDatabases.txt +++ b/Tests.Common/TestDatabases.txt @@ -6,7 +6,7 @@ #To achieve this, you can run DatabaseCreation.exe with argument 1 being your ServerName #You can apply a prefix e.g. TEST_ as an argument to DatabaseCreation.exe and include that prefix below if you like -Prefix: TEST_ +Prefix: RDMP_ ServerName: (localdb)\MSSQLLocalDB #Uncomment to run tests with a YamlRepository #UseFileSystemRepo: true From 020c11f97e07403b056e9c082b7b226e477393af Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 10:28:19 +0000 Subject: [PATCH 40/64] add find tests --- ...ecuteCommandIdentifyCHIInCatalogueTests.cs | 112 +++++++++++++++--- .../ExecuteCommandRevertRedactedCHITests.cs | 70 +++++------ Tests.Common/TestDatabases.txt | 2 +- 3 files changed, 134 insertions(+), 50 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs index e8d1d3478a..e28c18dd3c 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs @@ -1,7 +1,13 @@ -using NUnit.Framework; +using FAnsi; +using NUnit.Framework; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.CommandLine.DatabaseCreation; using Rdmp.Core.Curation.Data; +using Rdmp.Core.ReusableLibraryCode.Checks; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,24 +15,102 @@ namespace Rdmp.Core.Tests.CommandExecution; -internal class ExecuteCommandIdentifyCHIInCatalogueTests: DatabaseTests +internal class ExecuteCommandIdentifyCHIInCatalogueTests : DatabaseTests { - //bail out early - //allow list [Test] public void IdentifyCHIInCatalogue_Correct() { - // how do we create a catalogue with data? - //var _catalogue1 = new Catalogue(CatalogueRepository, "Dataset1"); - //var _catalogueItem1 = new CatalogueItem(CatalogueRepository, _catalogue1, "Some Value"); - //var _table1 = new TableInfo(CatalogueRepository, "Table1"); - //var _columnInfo1 = new ColumnInfo(CatalogueRepository, "Some Value", "varchar(10)", _table1); - //_catalogue1.SaveToDatabase(); - //_catalogueItem1.SaveToDatabase(); - //_columnInfo1.SaveToDatabase(); - - + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var cmd = new ExecuteCommandIdentifyCHIInCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, false, null); + Assert.DoesNotThrow(() => cmd.Execute()); + Assert.That(cmd.foundChis.Rows.Count, Is.EqualTo(0)); + } + + [Test] + public void IdentifyCHIInCatalogue_FoundCHI() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + + + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = '1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var cmd = new ExecuteCommandIdentifyCHIInCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, false, null); + Assert.DoesNotThrow(() => cmd.Execute()); + Assert.That(cmd.foundChis.Rows.Count, Is.EqualTo(2)); + } + + [Test] + public void IdentifyCHIInCatalogue_FoundCHI_BailOuit() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + + + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = '1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var cmd = new ExecuteCommandIdentifyCHIInCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, true, null); + Assert.DoesNotThrow(() => cmd.Execute()); + Assert.That(cmd.foundChis.Rows.Count, Is.EqualTo(1)); + } + + [Test] + public void IdentifyCHIInCatalogue_FoundCHI_AllowList() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + + //var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + //pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + + + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = '1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var tempFileToCreate = Path.Combine(TestContext.CurrentContext.TestDirectory, "allowList.yaml"); + var allowList = File.Create(tempFileToCreate); + allowList.Close(); + File.WriteAllLines(tempFileToCreate, new string[] { "RDMP_ALL:", "- SampleType" }); + var cmd = new ExecuteCommandIdentifyCHIInCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, false, tempFileToCreate); + Assert.DoesNotThrow(() => cmd.Execute()); + Assert.That(cmd.foundChis.Rows.Count, Is.EqualTo(0)); } } diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index 7292d47e4e..731e39b8b8 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -1,39 +1,39 @@ -//using NUnit.Framework; -//using Rdmp.Core.CommandExecution.AtomicCommands; -//using Rdmp.Core.CommandExecution; -//using Rdmp.Core.Curation.Data; -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; -//using Tests.Common; +using NUnit.Framework; +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.Curation.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tests.Common; -//namespace Rdmp.Core.Tests.CommandExecution; +namespace Rdmp.Core.Tests.CommandExecution; -//internal class ExecuteCommandRevertRedactedCHITests: DatabaseTests -//{ -// [Test] -// public void RevertRedactedCHI_Valid() -// { -// var redactedChisCount = CatalogueRepository.GetAllObjects().Length; -// var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); -// rCHI.SaveToDatabase(); -// //needs a columinfo and read data to update +internal class ExecuteCommandRevertRedactedCHITests : DatabaseTests +{ + //[Test] + //public void RevertRedactedCHI_Valid() + //{ + // var redactedChisCount = CatalogueRepository.GetAllObjects().Length; + // var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); + // rCHI.SaveToDatabase(); + // //needs a columinfo and read data to update -// var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); -// Assert.DoesNotThrow(() => cmd.Execute()); -// Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); -// } + // var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); + // Assert.DoesNotThrow(() => cmd.Execute()); + // Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); + //} -// [Test] -// public void RevertRedactedCHI_InValid() -// { -// var redactedChisCount = CatalogueRepository.GetAllObjects().Length; -// var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); -// rCHI.DeleteInDatabase(); -// var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); -// Assert.DoesNotThrow(() => cmd.Execute()); -// Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); -// } -//} + //[Test] + //public void RevertRedactedCHI_InValid() + //{ + // var redactedChisCount = CatalogueRepository.GetAllObjects().Length; + // var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); + // rCHI.DeleteInDatabase(); + // var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); + // Assert.DoesNotThrow(() => cmd.Execute()); + // Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); + //} +} diff --git a/Tests.Common/TestDatabases.txt b/Tests.Common/TestDatabases.txt index 168e37154c..bc2a95b27d 100644 --- a/Tests.Common/TestDatabases.txt +++ b/Tests.Common/TestDatabases.txt @@ -6,7 +6,7 @@ #To achieve this, you can run DatabaseCreation.exe with argument 1 being your ServerName #You can apply a prefix e.g. TEST_ as an argument to DatabaseCreation.exe and include that prefix below if you like -Prefix: RDMP_ +Prefix: TEST_ ServerName: (localdb)\MSSQLLocalDB #Uncomment to run tests with a YamlRepository #UseFileSystemRepo: true From 65c1500243d838c78a4524c32b67b991a825fa42 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 11:32:35 +0000 Subject: [PATCH 41/64] redact chi test --- ...cuteCommandRedactCHIsFromCatalogueTests.cs | 89 +++++++++++++++++++ .../ExecuteCommandRedactCHIsFromCatalogue.cs | 66 +------------- 2 files changed, 90 insertions(+), 65 deletions(-) create mode 100644 Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs new file mode 100644 index 0000000000..6c8a954b7a --- /dev/null +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -0,0 +1,89 @@ +using NUnit.Framework; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands; +using Rdmp.Core.CommandLine.DatabaseCreation; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.ReusableLibraryCode.Checks; +using Spectre.Console.Rendering; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tests.Common; + +namespace Rdmp.Core.Tests.CommandExecution; + +internal class ExecuteCommandRedactCHIsFromCatalogueTests: DatabaseTests +{ + + [Test] + public void RedactCHIsFromCatalogue_ValidSingle() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = 'F1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, null); + Assert.DoesNotThrow(() => cmd.Execute()); + var dt = new DataTable(); + using (var con = tbl.Database.Server.GetConnection()) + { + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) + { + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); + } + } + Assert.That(dt.Rows.Count, Is.EqualTo(2)); + } + + [Test] + public void RedactCHIsFromCatalogue_Allowlist() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = 'F1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + var tempFileToCreate = Path.Combine(TestContext.CurrentContext.TestDirectory, "allowList.yaml"); + var allowList = File.Create(tempFileToCreate); + allowList.Close(); + File.WriteAllLines(tempFileToCreate, new string[] { "RDMP_ALL:", "- SampleType" }); + var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, tempFileToCreate); + Assert.DoesNotThrow(() => cmd.Execute()); + var dt = new DataTable(); + using (var con = tbl.Database.Server.GetConnection()) + { + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) + { + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); + } + } + Assert.That(dt.Rows.Count, Is.EqualTo(0)); + } +} diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index 18f9efec51..2bbbc1968c 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -38,38 +38,8 @@ public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [Dem _catalogue = catalogue; _activator = activator; _allowListLocation = allowListLocation; - //if (!string.IsNullOrWhiteSpace(allowListLocation)) - //{ - // var allowListFileContent = File.ReadAllText(allowListLocation); - // var deserializer = new DeserializerBuilder().Build(); - // var yamlObject = deserializer.Deserialize>>(allowListFileContent); - // foreach (var (cat, columns) in yamlObject) - // { - // _allowLists.Add(cat, columns); - // } - //} } - //private void handleFoundCHI(string foundChi, string table, string column, string columnValue) - //{ - // //check for PK - // Console.WriteLine("Found CHI!"); - // redactionCount++; - // var rc = new RedactedCHI(_activator.RepositoryLocator.CatalogueRepository, foundChi, ExecuteCommandIdentifyCHIInCatalogue.WrapCHIInContext(foundChi, columnValue, 20), $"{table}.{column}"); - // rc.SaveToDatabase(); - // var redactedValue = columnValue.Replace(foundChi, $"REDACTED_CHI_{rc.ID}"); - // //TODO can be smarted about how we wrote tothe db, can share a connection etc - // var sql = $"UPDATE {table} SET {column}='{redactedValue}' where {column}='{columnValue}'"; - // var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - // var conn = server.GetConnection(); - // conn.Open(); - // using (var cmd = server.GetCommand(sql, conn)) - // { - // cmd.ExecuteNonQuery(); - // conn.Close(); - // } - //} - - + public override void Execute() { base.Execute(); @@ -112,39 +82,5 @@ public override void Execute() } } } - //List columnAllowList = new(); - //if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) - // columnAllowList.AddRange(_extractionSpecificAllowances); - //if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) - // columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); - //foreach (var item in _catalouge.CatalogueItems) - //{ - // if (columnAllowList.Contains(item.Name)) continue; - - // var column = item.ColumnInfo.Name; - // int idxOfLastSplit = column.LastIndexOf('.'); - // string table = column[..idxOfLastSplit]; - // var columnName = column[(idxOfLastSplit + 1)..]; - // var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - // var sql = $"SELECT {columnName} from {table}"; - // var dt = new DataTable(); - // dt.BeginLoadData(); - // using (var cmd = server.GetCommand(sql, server.GetConnection())) - // { - // using var da = server.GetDataAdapter(cmd); - // da.Fill(dt); - // } - // dt.EndLoadData(); - // foreach (DataRow row in dt.Rows) - // { - - // var value = row[dt.Columns[0].ColumnName].ToString(); - // var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); - // if (!string.IsNullOrWhiteSpace(potentialCHI)) - // { - // handleFoundCHI(potentialCHI, table,columnName,value); - // } - // } - //} } } From 22dbd14d204af1fb0bf672eb657e22cab0ca7559 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 13:14:02 +0000 Subject: [PATCH 42/64] update tests --- .../ExecuteCommandRevertRedactedCHITests.cs | 119 ++++++++++++++---- 1 file changed, 97 insertions(+), 22 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index 731e39b8b8..4e43aaac03 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -8,32 +8,107 @@ using System.Text; using System.Threading.Tasks; using Tests.Common; +using Rdmp.Core.CommandLine.DatabaseCreation; +using Rdmp.Core.ReusableLibraryCode.Checks; +using System.Data; namespace Rdmp.Core.Tests.CommandExecution; internal class ExecuteCommandRevertRedactedCHITests : DatabaseTests { - //[Test] - //public void RevertRedactedCHI_Valid() - //{ - // var redactedChisCount = CatalogueRepository.GetAllObjects().Length; - // var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); - // rCHI.SaveToDatabase(); - // //needs a columinfo and read data to update + [Test] + public void RevertRedactedCHI_Valid() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (1) {tbl.GetRuntimeName()} set SampleType = 'R1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, null); + Assert.DoesNotThrow(() => cmd.Execute()); + var dt = new DataTable(); + using (var con = tbl.Database.Server.GetConnection()) + { + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'R#########1'", con)) + { + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); + } + } + Assert.That(dt.Rows.Count, Is.EqualTo(1)); + var redactedChisCount = CatalogueRepository.GetAllObjects().Length; + var rCHI = CatalogueRepository.GetAllObjects().First(); + var revertCmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); + Assert.DoesNotThrow(() => revertCmd.Execute()); + Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length +1)); + dt = new DataTable(); + using (var con = tbl.Database.Server.GetConnection()) + { + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'R1111111111'", con)) + { + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); + } + } + Assert.That(dt.Rows.Count, Is.EqualTo(1)); + } - // var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); - // Assert.DoesNotThrow(() => cmd.Execute()); - // Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); - //} + [Test] + public void RevertRedactedCHI_InValid() + { + var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); + creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, + new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); + var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); + var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); + var updateSQL = $"UPDATE top (1) {tbl.GetRuntimeName()} set SampleType = 'R1111111111'"; + using (var con = tbl.Database.Server.GetConnection()) + { + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, null); + Assert.DoesNotThrow(() => cmd.Execute()); + var dt = new DataTable(); + using (var con = tbl.Database.Server.GetConnection()) + { + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'R#########1'", con)) + { + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); + } + } + Assert.That(dt.Rows.Count, Is.EqualTo(1)); + var redactedChisCount = CatalogueRepository.GetAllObjects().Length; + var rCHI = CatalogueRepository.GetAllObjects().First(); + var revertCmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); + Assert.DoesNotThrow(() => revertCmd.Execute()); + Assert.DoesNotThrow(() => revertCmd.Execute()); + Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length + 1)); + dt = new DataTable(); + using (var con = tbl.Database.Server.GetConnection()) + { + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'R1111111111'", con)) + { + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); + } + } + Assert.That(dt.Rows.Count, Is.EqualTo(1)); - //[Test] - //public void RevertRedactedCHI_InValid() - //{ - // var redactedChisCount = CatalogueRepository.GetAllObjects().Length; - // var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); - // rCHI.DeleteInDatabase(); - // var cmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); - // Assert.DoesNotThrow(() => cmd.Execute()); - // Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); - //} -} + } + } From ab0ad56ac9881774c4f9fa382125f2790d684ad4 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 14:02:43 +0000 Subject: [PATCH 43/64] update tests --- .../Engine/Integration/ExecuteCHIRedactionStageTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs index 25aed15301..5e85129731 100644 --- a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs +++ b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs @@ -37,7 +37,6 @@ public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) } var job = Substitute.For(); - //job.RegularTablesToLoad.Returns(new List(new[] { db.DiscoverTables })); var task = new ExecuteCHIRedactionStage(job, db, LoadStage.AdjustRaw); task.Execute(true); using (var con = db.Server.GetConnection()) @@ -50,7 +49,6 @@ public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); da.Fill(dt); Assert.That("##########", Is.EqualTo(dt.Rows[0][0])); - } } } From efafd0eff82a33f1089f9ccf51e01f6763b05540 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 14:40:36 +0000 Subject: [PATCH 44/64] remove prohibited word --- .../ExecuteCommandIdentifyCHIInCatalogueTests.cs | 2 +- .../ExecuteCommandRedactCHIsFromCatalogueTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs index e28c18dd3c..4feee47194 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs @@ -107,7 +107,7 @@ public void IdentifyCHIInCatalogue_FoundCHI_AllowList() var tempFileToCreate = Path.Combine(TestContext.CurrentContext.TestDirectory, "allowList.yaml"); var allowList = File.Create(tempFileToCreate); allowList.Close(); - File.WriteAllLines(tempFileToCreate, new string[] { "RDMP_ALL:", "- SampleType" }); + File.WriteAllLines(tempFileToCreate, new string[] { "Biochemistry:", "- SampleType" }); var cmd = new ExecuteCommandIdentifyCHIInCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, false, tempFileToCreate); Assert.DoesNotThrow(() => cmd.Execute()); Assert.That(cmd.foundChis.Rows.Count, Is.EqualTo(0)); diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 6c8a954b7a..8ad8d4168f 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -72,7 +72,7 @@ public void RedactCHIsFromCatalogue_Allowlist() var tempFileToCreate = Path.Combine(TestContext.CurrentContext.TestDirectory, "allowList.yaml"); var allowList = File.Create(tempFileToCreate); allowList.Close(); - File.WriteAllLines(tempFileToCreate, new string[] { "RDMP_ALL:", "- SampleType" }); + File.WriteAllLines(tempFileToCreate, new string[] { "Biochemistry:", "- SampleType" }); var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, tempFileToCreate); Assert.DoesNotThrow(() => cmd.Execute()); var dt = new DataTable(); From 87a66d383e207892ec0afdb9e45f0fcbef5fdb19 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 14:58:11 +0000 Subject: [PATCH 45/64] tidy up code --- .../ResearchDataManagementPlatform.csproj | 5 ++--- .../ExecuteCommandConfirmRedactedCHITests.cs | 6 +----- .../ExecuteCommandIdentifyCHIInCatalogueTests.cs | 11 +---------- ...ExecuteCommandRedactCHIsFromCatalogueTests.cs | 5 ----- .../ExecuteCommandRevertRedactedCHITests.cs | 4 ---- .../Integration/ExecuteCHIRedactionStageTests.cs | 8 +------- .../ExecuteCommandConfirmRedactedCHI.cs | 6 +++--- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 16 +++------------- 8 files changed, 11 insertions(+), 50 deletions(-) diff --git a/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj b/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj index 1e755f74aa..e5798ad5aa 100644 --- a/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj +++ b/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj @@ -35,8 +35,8 @@ - - + + RDMPMainForm.cs @@ -97,7 +97,6 @@ WindowManagement\Licenses\LIBRARYLICENSES - SettingsSingleFileGenerator Settings.Designer.cs diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs index 054456ee9c..7e33aaa8fb 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs @@ -2,11 +2,6 @@ using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Tests.Common; namespace Rdmp.Core.Tests.CommandExecution; @@ -29,6 +24,7 @@ public void ConfirmRedactedCHI_InValid() { var redactedChisCount = CatalogueRepository.GetAllObjects().Length; var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); rCHI.DeleteInDatabase(); + var cmd = new ExecuteCommandConfirmRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); Assert.DoesNotThrow(() => cmd.Execute()); Assert.That(redactedChisCount, Is.EqualTo(CatalogueRepository.GetAllObjects().Length)); diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs index 4feee47194..548e7ed64e 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandIdentifyCHIInCatalogueTests.cs @@ -1,16 +1,11 @@ -using FAnsi; -using NUnit.Framework; +using NUnit.Framework; using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.CommandLine.DatabaseCreation; using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.Checks; -using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Tests.Common; namespace Rdmp.Core.Tests.CommandExecution; @@ -85,10 +80,6 @@ public void IdentifyCHIInCatalogue_FoundCHI_BailOuit() public void IdentifyCHIInCatalogue_FoundCHI_AllowList() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); - - //var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); - //pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); - var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 8ad8d4168f..4cf89f70c9 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -4,14 +4,9 @@ using Rdmp.Core.CommandLine.DatabaseCreation; using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.Checks; -using Spectre.Console.Rendering; -using System; -using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Tests.Common; namespace Rdmp.Core.Tests.CommandExecution; diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index 4e43aaac03..afef258b20 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -2,11 +2,7 @@ using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.CommandExecution; using Rdmp.Core.Curation.Data; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Tests.Common; using Rdmp.Core.CommandLine.DatabaseCreation; using Rdmp.Core.ReusableLibraryCode.Checks; diff --git a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs index 5e85129731..951901365d 100644 --- a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs +++ b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs @@ -2,16 +2,10 @@ using Microsoft.Data.SqlClient; using NSubstitute; using NUnit.Framework; -using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.DataLoad; using Rdmp.Core.DataLoad.Engine.Job; using Rdmp.Core.DataLoad.Engine.LoadExecution.Components.Runtime; -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Tests.Common; namespace Rdmp.Core.Tests.DataLoad.Engine.Integration; @@ -48,7 +42,7 @@ public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) var dt = new DataTable(); SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); da.Fill(dt); - Assert.That("##########", Is.EqualTo(dt.Rows[0][0])); + Assert.That(dt.Rows[0][0], Is.EqualTo("##########")); } } } diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs index f0c463934c..1a406f91f0 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandConfirmRedactedCHI.cs @@ -9,10 +9,10 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandConfirmRedactedCHI : BasicCommandExecution, IAtomicCommand { - readonly RedactedCHI _redactedCHI; - public ExecuteCommandConfirmRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHI to confirm")]RedactedCHI redaction): base(activator) + private readonly RedactedCHI _redactedCHI; + public ExecuteCommandConfirmRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHI to confirm")]RedactedCHI redactedCHI): base(activator) { - _redactedCHI = redaction; + _redactedCHI = redactedCHI; } public override void Execute() diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 8ca8c849a7..752a3c59a2 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -3,21 +3,14 @@ // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; -using Org.BouncyCastle.Asn1.Crmf; -using Rdmp.Core.CommandExecution; -using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.Core.DataFlowPipeline; -using Rdmp.Core.DataViewing; using Rdmp.Core.ReusableLibraryCode.DataAccess; using System; using System.Collections.Generic; using System.Data; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using YamlDotNet.Serialization; namespace Rdmp.Core.CommandExecution.AtomicCommands; @@ -25,16 +18,13 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandIdentifyCHIInCatalogue : BasicCommandExecution, IAtomicCommand { - private ICatalogue _catalouge; - private IBasicActivateItems _activator; - private bool _bailOutEarly; + private readonly ICatalogue _catalouge; + private readonly bool _bailOutEarly; private readonly Dictionary> _allowLists = new(); - //TODO don;t show PKs public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, bool bailOutEarly = false, string allowListLocation = null) : base(activator) { _catalouge = catalogue; - _activator = activator; _bailOutEarly = bailOutEarly; if (!string.IsNullOrWhiteSpace(allowListLocation)) { @@ -130,7 +120,7 @@ public override void Execute() Console.WriteLine($"Found {foundChis.Rows.Count} CHIs in the {_catalouge.Name} Catalogue."); foreach (DataRow row in foundChis.Rows) { - Console.WriteLine($"{row["potential CHI"]} | {row["Context"]} | {row["Source Column Name"]}"); + Console.WriteLine($"{row["Potential CHI"]} | {row["Context"]} | {row["Source Column Name"]}"); } } From eabe9553e0dc02e862ffd48810d2789a80642999 Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 13 Dec 2023 16:14:00 +0000 Subject: [PATCH 46/64] comment out test for build testing purposes --- .../ExecuteCHIRedactionStageTests.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs index 951901365d..f4f4d4ffbd 100644 --- a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs +++ b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs @@ -17,32 +17,32 @@ internal class ExecuteCHIRedactionStageTests: DatabaseTests public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) { - var db = GetCleanedServer(dbType); - using (var con = db.Server.GetConnection()) - { - con.Open(); + //var db = GetCleanedServer(dbType); + //using (var con = db.Server.GetConnection()) + //{ + // con.Open(); - var cmdCreateTable = db.Server.GetCommand( - $"CREATE Table {db.GetRuntimeName()} (SomeValue varchar(10))", con); - cmdCreateTable.ExecuteNonQuery(); - var data = db.Server.GetCommand( - $"insert into {db.GetRuntimeName()} (SomeValue) values(1111111111) ", con); - data.ExecuteNonQuery(); - } + // var cmdCreateTable = db.Server.GetCommand( + // $"CREATE Table {db.GetRuntimeName()} (SomeValue varchar(10))", con); + // cmdCreateTable.ExecuteNonQuery(); + // var data = db.Server.GetCommand( + // $"insert into {db.GetRuntimeName()} (SomeValue) values(1111111111) ", con); + // data.ExecuteNonQuery(); + //} - var job = Substitute.For(); - var task = new ExecuteCHIRedactionStage(job, db, LoadStage.AdjustRaw); - task.Execute(true); - using (var con = db.Server.GetConnection()) - { - con.Open(); + //var job = Substitute.For(); + //var task = new ExecuteCHIRedactionStage(job, db, LoadStage.AdjustRaw); + //task.Execute(true); + //using (var con = db.Server.GetConnection()) + //{ + // con.Open(); - var cmdCreateTable = db.Server.GetCommand( - $"select SomeValue {db.GetRuntimeName()}", con); - var dt = new DataTable(); - SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); - da.Fill(dt); - Assert.That(dt.Rows[0][0], Is.EqualTo("##########")); - } + // var cmdCreateTable = db.Server.GetCommand( + // $"select SomeValue {db.GetRuntimeName()}", con); + // var dt = new DataTable(); + // SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); + // da.Fill(dt); + // Assert.That(dt.Rows[0][0], Is.EqualTo("##########")); + //} } } From 8307a7354f4e5d89438451e0543a960e48defe3e Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 14 Dec 2023 08:16:59 +0000 Subject: [PATCH 47/64] update tests --- .../ExecuteCommandRedactCHIsFromCatalogueTests.cs | 4 ++-- .../CommandExecution/ExecuteCommandRevertRedactedCHITests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 4cf89f70c9..48b61d1cc0 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -18,8 +18,8 @@ internal class ExecuteCommandRedactCHIsFromCatalogueTests: DatabaseTests public void RedactCHIsFromCatalogue_ValidSingle() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); - var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); - pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + //var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + //pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index afef258b20..41bb289e39 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -16,8 +16,8 @@ internal class ExecuteCommandRevertRedactedCHITests : DatabaseTests public void RevertRedactedCHI_Valid() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); - var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); - pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + //var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + //pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); From d5520b84e684e6654da9dd679059268fa9e92b0e Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 14 Dec 2023 16:18:37 +0000 Subject: [PATCH 48/64] working test --- .../ExecuteCHIRedactionStageTests.cs | 81 ++++++++++++------- .../Runtime/ExecuteCHIRedactionStage.cs | 6 +- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs index f4f4d4ffbd..120eeea7b1 100644 --- a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs +++ b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs @@ -2,9 +2,14 @@ using Microsoft.Data.SqlClient; using NSubstitute; using NUnit.Framework; +using Rdmp.Core.Curation; +using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.DataLoad; +using Rdmp.Core.DataLoad.Engine.DatabaseManagement.EntityNaming; using Rdmp.Core.DataLoad.Engine.Job; using Rdmp.Core.DataLoad.Engine.LoadExecution.Components.Runtime; +using System; +using System.Collections.Generic; using System.Data; using Tests.Common; @@ -12,37 +17,57 @@ namespace Rdmp.Core.Tests.DataLoad.Engine.Integration; internal class ExecuteCHIRedactionStageTests: DatabaseTests { - [TestCase(DatabaseType.MySql)] + //[TestCase(DatabaseType.MySql)] [TestCase(DatabaseType.MicrosoftSQLServer)] public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) { - //var db = GetCleanedServer(dbType); - //using (var con = db.Server.GetConnection()) - //{ - // con.Open(); - - // var cmdCreateTable = db.Server.GetCommand( - // $"CREATE Table {db.GetRuntimeName()} (SomeValue varchar(10))", con); - // cmdCreateTable.ExecuteNonQuery(); - // var data = db.Server.GetCommand( - // $"insert into {db.GetRuntimeName()} (SomeValue) values(1111111111) ", con); - // data.ExecuteNonQuery(); - //} - - //var job = Substitute.For(); - //var task = new ExecuteCHIRedactionStage(job, db, LoadStage.AdjustRaw); - //task.Execute(true); - //using (var con = db.Server.GetConnection()) - //{ - // con.Open(); - - // var cmdCreateTable = db.Server.GetCommand( - // $"select SomeValue {db.GetRuntimeName()}", con); - // var dt = new DataTable(); - // SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); - // da.Fill(dt); - // Assert.That(dt.Rows[0][0], Is.EqualTo("##########")); - //} + var db = GetCleanedServer(dbType); + var dt = new DataTable("Test_Redaction"); + dt.Columns.Add("SomeValue"); + dt.Columns.Add("SomeOtherValue"); + dt.Rows.Add("1111111111","F1111111111"); + + var tbl = db.CreateTable(dt.TableName, dt); + var importer = new TableInfoImporter(CatalogueRepository, tbl); + importer.DoImport(out var tableInfo, out var colInfos); + var cat = new Catalogue(CatalogueRepository,"Test_Redaction"); + cat.SaveToDatabase(); + var ci = new CatalogueItem(CatalogueRepository,cat, "SomeValue"); + ci.SaveToDatabase(); + var coi = new ColumnInfo(CatalogueRepository, "Test_Redaction.SomeValue", "varchar(10)",tableInfo); + coi.IsPrimaryKey = true; + coi.SaveToDatabase(); + ci.ColumnInfo_ID = coi.ID; + ci.SaveToDatabase(); + + var ci2 = new CatalogueItem(CatalogueRepository, cat, "SomeOtherValue"); + ci2.SaveToDatabase(); + var coi2 = new ColumnInfo(CatalogueRepository, "Test_Redaction.SomeOtherValue", "varchar(11)", tableInfo); + coi2.IsPrimaryKey = false; + coi2.SaveToDatabase(); + ci2.ColumnInfo_ID = coi.ID; + ci2.SaveToDatabase(); + + + + var job = Substitute.For(); + job.RegularTablesToLoad.Returns(new List() {tableInfo }); + job.RepositoryLocator.Returns(RepositoryLocator); + job.Configuration.Returns(new HICDatabaseConfiguration(db.Server, null, null, null)); + //to the job is junk + var task = new ExecuteCHIRedactionStage(job, db, LoadStage.AdjustRaw); + task.Execute(true); + using (var con = db.Server.GetConnection()) + { + con.Open(); + + var cmdCreateTable = db.Server.GetCommand( + $"select SomeOtherValue from {db.GetRuntimeName()}.dbo.Test_Redaction", con); + dt = new DataTable(); + SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); + da.Fill(dt); + Assert.That(dt.Rows[0][0], Is.EqualTo("F#########1")); + } } } diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 9a74b857e6..256e3d43f2 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -63,10 +63,10 @@ private void RedactCHIs(ITableInfo tableInfo) $"About to run {GetType()} mutilation on table {tbl}")); var dt = tbl.GetDataTable(); List _allowList = new(); - if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) + if (_allowLists is not null && _allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) _allowList.AddRange(_extractionSpecificAllowances); var y = tbl.ToString(); - if (_allowLists.TryGetValue(tbl.ToString(), out var _catalogueSpecificAllowances)) + if (_allowLists is not null && _allowLists.TryGetValue(tbl.ToString(), out var _catalogueSpecificAllowances)) _allowList.AddRange(_catalogueSpecificAllowances.ToList()); foreach (DataColumn col in dt.Columns) { @@ -86,6 +86,7 @@ private void RedactCHIs(ITableInfo tableInfo) if (catalogue != null) { //this can probably be tidied up + Console.WriteLine(foundTable); var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey && x.Name.Contains(foundTable)).First(); //there may be more, but we just need one pkColumnName = pkColumnInfo.Name; if (pkColumnInfo != null) @@ -102,6 +103,7 @@ in dt.Columns.Cast() ft = ft.Replace("..", ".[dbo]."); if (pkColumnName.Split(".").Last().Replace("[","").Replace("]","") != $"{col.ColumnName}") { + Console.WriteLine("hi"); var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); rc.SaveToDatabase(); var redactionString = "##########"; From cbad896624690910d6548295b08aa06c9e60381a Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 15 Dec 2023 13:27:20 +0000 Subject: [PATCH 49/64] add no results found label --- .../ResearchDataManagementPlatform.csproj | 224 +++++++++--------- .../ExecuteCommandConfirmRedactedCHITests.cs | 4 +- ...cuteCommandRedactCHIsFromCatalogueTests.cs | 4 +- .../ExecuteCommandRevertRedactedCHITests.cs | 5 +- .../ExecuteCHIRedactionStageTests.cs | 3 + .../ExecuteCommandIdentifyCHIInCatalogue.cs | 92 ++++--- .../RedactChisInCatalogueDialog.Designer.cs | 12 + .../RedactChisInCatalogueDialog.cs | 6 +- Rdmp.UI/SimpleDialogs/SelectDialog`1.resx | 63 ----- 9 files changed, 196 insertions(+), 217 deletions(-) delete mode 100644 Rdmp.UI/SimpleDialogs/SelectDialog`1.resx diff --git a/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj b/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj index e5798ad5aa..a59d4db3ad 100644 --- a/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj +++ b/Application/ResearchDataManagementPlatform/ResearchDataManagementPlatform.csproj @@ -1,114 +1,114 @@ - - {550988FD-F1FA-41D8-BE0F-00B4DE47D320} - WinExe - net7.0-windows - true - true - true - false - 1701;1702;CS1591;NU1701 - embedded - true - true - true - - - - - - true - Icon\main.ico - true - - - - - - - - - - - - - - - - - - - RDMPMainForm.cs - - - - HomeUI.cs - - - Form - - - LicenseUI.cs - - - UserControl - - - RDMPTaskBarUI.cs - - - - RDMPTopMenuStripUI.cs - - - - - RDMPMainForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SourceCodeForSelfAwareness.zip - PreserveNewest - - - HomeUI.cs - - - LicenseUI.cs - - - RDMPTaskBarUI.cs - - - RDMPTopMenuStripUI.cs - - - WindowManagement\Licenses\LICENSE - - - WindowManagement\Licenses\LIBRARYLICENSES - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - True - - - - - + + {550988FD-F1FA-41D8-BE0F-00B4DE47D320} + WinExe + net7.0-windows + true + true + true + false + 1701;1702;CS1591;NU1701 + embedded + true + true + true + + + + + + true + Icon\main.ico + true + + + + + + + + + + + + + + + + + + + RDMPMainForm.cs + + + + HomeUI.cs + + + Form + + + LicenseUI.cs + + + UserControl + + + RDMPTaskBarUI.cs + + + + RDMPTopMenuStripUI.cs + + + + + RDMPMainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SourceCodeForSelfAwareness.zip + PreserveNewest + + + HomeUI.cs + + + LicenseUI.cs + + + RDMPTaskBarUI.cs + + + RDMPTopMenuStripUI.cs + + + WindowManagement\Licenses\LICENSE + + + WindowManagement\Licenses\LIBRARYLICENSES + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + True + + + + + diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs index 7e33aaa8fb..222d272e5b 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandConfirmRedactedCHITests.cs @@ -11,7 +11,7 @@ internal class ExecuteCommandConfirmRedactedCHITests: DatabaseTests [Test] public void ConfirmRedactedCHI_Valid() { var redactedChisCount = CatalogueRepository.GetAllObjects().Length; - var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]","pkValue","[pkColumn]","[columnName]"); + var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake].[table]","pkValue","[pkColumn]","[columnName]"); rCHI.SaveToDatabase(); var cmd = new ExecuteCommandConfirmRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); @@ -22,7 +22,7 @@ public void ConfirmRedactedCHI_Valid() { [Test] public void ConfirmRedactedCHI_InValid() { var redactedChisCount = CatalogueRepository.GetAllObjects().Length; - var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake],[table]", "pkValue", "[pkColumn]", "[columnName]"); + var rCHI = new RedactedCHI(CatalogueRepository, "1111111111", 0, "[fake].[table]", "pkValue", "[pkColumn]", "[columnName]"); rCHI.DeleteInDatabase(); var cmd = new ExecuteCommandConfirmRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 48b61d1cc0..3a9ce17bd2 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -18,8 +18,6 @@ internal class ExecuteCommandRedactCHIsFromCatalogueTests: DatabaseTests public void RedactCHIsFromCatalogue_ValidSingle() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); - //var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); - //pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); @@ -44,6 +42,7 @@ public void RedactCHIsFromCatalogue_ValidSingle() } } Assert.That(dt.Rows.Count, Is.EqualTo(2)); + dt.Dispose(); } [Test] @@ -80,5 +79,6 @@ public void RedactCHIsFromCatalogue_Allowlist() } } Assert.That(dt.Rows.Count, Is.EqualTo(0)); + dt.Dispose(); } } diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index 41bb289e39..c38e497293 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -16,8 +16,6 @@ internal class ExecuteCommandRevertRedactedCHITests : DatabaseTests public void RevertRedactedCHI_Valid() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); - //var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); - //pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); @@ -57,6 +55,7 @@ public void RevertRedactedCHI_Valid() } } Assert.That(dt.Rows.Count, Is.EqualTo(1)); + dt.Dispose(); } [Test] @@ -89,6 +88,7 @@ public void RevertRedactedCHI_InValid() } } Assert.That(dt.Rows.Count, Is.EqualTo(1)); + dt.Dispose(); var redactedChisCount = CatalogueRepository.GetAllObjects().Length; var rCHI = CatalogueRepository.GetAllObjects().First(); var revertCmd = new ExecuteCommandRevertRedactedCHI(new ThrowImmediatelyActivator(RepositoryLocator), rCHI); @@ -105,6 +105,7 @@ public void RevertRedactedCHI_InValid() } } Assert.That(dt.Rows.Count, Is.EqualTo(1)); + dt.Dispose(); } } diff --git a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs index 120eeea7b1..ef41d18792 100644 --- a/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs +++ b/Rdmp.Core.Tests/DataLoad/Engine/Integration/ExecuteCHIRedactionStageTests.cs @@ -64,10 +64,13 @@ public void ExecuteCHIRedactionStage_Basic(DatabaseType dbType) var cmdCreateTable = db.Server.GetCommand( $"select SomeOtherValue from {db.GetRuntimeName()}.dbo.Test_Redaction", con); + dt.Dispose(); dt = new DataTable(); SqlDataAdapter da = new SqlDataAdapter((SqlCommand)cmdCreateTable); da.Fill(dt); + da.Dispose(); Assert.That(dt.Rows[0][0], Is.EqualTo("F#########1")); } + dt.Dispose(); } } diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 752a3c59a2..26d8c23d24 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -26,6 +26,12 @@ public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [Dema { _catalouge = catalogue; _bailOutEarly = bailOutEarly; + foundChis.Columns.Add(PotentialCHI); + foundChis.Columns.Add(Context); + foundChis.Columns.Add(SourceColumnName); + foundChis.Columns.Add(PKValue); + foundChis.Columns.Add(PKColumn); + foundChis.Columns.Add(ReplacementIndex); if (!string.IsNullOrWhiteSpace(allowListLocation)) { var allowListFileContent = File.ReadAllText(allowListLocation); @@ -46,20 +52,28 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 } + private readonly string PotentialCHI = "Potential CHI"; + private readonly string Context = "Context"; + private readonly string SourceColumnName = "Source Column Name"; + private readonly string PKValue = "PK Value"; + private readonly string PKColumn = "PK Column"; + private readonly string ReplacementIndex = "replacementIndex"; + private readonly string RDMP_ALL = "RDMP_ALL"; - private void handleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) + + private void HandleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) { if (pkColumn.Split(".").Last().Replace("[", "").Replace("]", "") == columnName.Replace("[", "").Replace("]", "")) return; //don't redact PKs, it gets messy - if (foundChis.Rows.Count == 0) - { - //init - foundChis.Columns.Add("Potential CHI"); - foundChis.Columns.Add("Context"); - foundChis.Columns.Add("Source Column Name"); - foundChis.Columns.Add("PK Value"); - foundChis.Columns.Add("PK Column"); - foundChis.Columns.Add("replacementIndex"); - } + //if (foundChis.Rows.Count == 0) + //{ + // // init + // foundChis.Columns.Add(PotentialCHI); + // foundChis.Columns.Add(Context); + // foundChis.Columns.Add(SourceColumnName); + // foundChis.Columns.Add(PKValue); + // foundChis.Columns.Add(PKColumn); + // foundChis.Columns.Add(ReplacementIndex); + //} var shrunkContext = WrapCHIInContext(foundChi, contextValue); foundChis.Rows.Add(foundChi, shrunkContext, columnName, pkValue, pkColumn, contextValue.IndexOf(foundChi)); } @@ -69,7 +83,7 @@ public override void Execute() { base.Execute(); List columnAllowList = new(); - if (_allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) + if (_allowLists.TryGetValue(RDMP_ALL, out var _extractionSpecificAllowances)) columnAllowList.AddRange(_extractionSpecificAllowances); if (_allowLists.TryGetValue(_catalouge.Name, out var _catalogueSpecificAllowances)) columnAllowList.AddRange(_catalogueSpecificAllowances.ToList()); @@ -88,44 +102,52 @@ public override void Execute() int idxOfLastSplit = column.LastIndexOf('.'); var columnName = column[(idxOfLastSplit + 1)..]; var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - var pkColumn = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First().Name.Split(".").Last(); - var sql = $"SELECT {columnName},{pkColumn} from {column[..idxOfLastSplit]}"; - var dt = new DataTable(); - dt.BeginLoadData(); - using (var cmd = server.GetCommand(sql, server.GetConnection())) - { - using var da = server.GetDataAdapter(cmd); - da.Fill(dt); - } - dt.EndLoadData(); - foreach (DataRow row in dt.Rows) + // what if there is no pk? + var pkColumns = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey); + if (pkColumns.Any()) { - var value = row[dt.Columns[0].ColumnName].ToString(); - var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); - if (!string.IsNullOrWhiteSpace(potentialCHI)) + var pkColumn = pkColumns.First().Name.Split(".").Last(); + var sql = $"SELECT {columnName},{pkColumn} from {column[..idxOfLastSplit]}"; + var dt = new DataTable(); + dt.BeginLoadData(); + using (var cmd = server.GetCommand(sql, server.GetConnection())) + { + using var da = server.GetDataAdapter(cmd); + da.Fill(dt); + } + dt.EndLoadData(); + foreach (DataRow row in dt.Rows) { - var pkColumnInfo = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); - var pkValue = getPKValue(pkColumnInfo, row, dt); - handleFoundCHI(potentialCHI, value, item.Name, pkValue, pkColumnInfo.Name); - if (_bailOutEarly) + + var value = row[dt.Columns[0].ColumnName].ToString(); + var potentialCHI = CHIColumnFinder.GetPotentialCHI(value); + if (!string.IsNullOrWhiteSpace(potentialCHI)) { - break; + var pkColumnInfo = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey).First(); + var pkValue = GetPKValue(pkColumnInfo, row, dt); + HandleFoundCHI(potentialCHI, value, item.Name, pkValue, pkColumnInfo.Name); + if (_bailOutEarly) + { + break; + } } } - - + } + else + { + Console.WriteLine("Unable to find a primary key for this catalogue"); } } Console.WriteLine($"Found {foundChis.Rows.Count} CHIs in the {_catalouge.Name} Catalogue."); foreach (DataRow row in foundChis.Rows) { - Console.WriteLine($"{row["Potential CHI"]} | {row["Context"]} | {row["Source Column Name"]}"); + Console.WriteLine($"{row[PotentialCHI]} | {row[Context]} | {row[SourceColumnName]}"); } } - private string getPKValue(ColumnInfo pkColumnInfo, DataRow row, DataTable dt) + private static string GetPKValue(ColumnInfo pkColumnInfo, DataRow row, DataTable dt) { //todo doesn't work with multitable catalogues diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs index ba9bbfa153..aa340cb1da 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.Designer.cs @@ -34,6 +34,7 @@ private void InitializeComponent() label1 = new System.Windows.Forms.Label(); tbAllowList = new System.Windows.Forms.TextBox(); lbResults = new System.Windows.Forms.Label(); + lblNoResultsFound = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)dgResults).BeginInit(); SuspendLayout(); // @@ -90,11 +91,21 @@ private void InitializeComponent() lbResults.Size = new System.Drawing.Size(0, 15); lbResults.TabIndex = 6; // + // lblNoResultsFound + // + lblNoResultsFound.AutoSize = true; + lblNoResultsFound.Location = new System.Drawing.Point(316, 66); + lblNoResultsFound.Name = "lblNoResultsFound"; + lblNoResultsFound.Size = new System.Drawing.Size(100, 15); + lblNoResultsFound.TabIndex = 7; + lblNoResultsFound.Text = "No Results Found"; + // // RedactChisInCatalogueDialog // AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; ClientSize = new System.Drawing.Size(800, 450); + Controls.Add(lblNoResultsFound); Controls.Add(lbResults); Controls.Add(tbAllowList); Controls.Add(label1); @@ -115,5 +126,6 @@ private void InitializeComponent() private System.Windows.Forms.Label label1; private System.Windows.Forms.TextBox tbAllowList; private System.Windows.Forms.Label lbResults; + private System.Windows.Forms.Label lblNoResultsFound; } } \ No newline at end of file diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index e3cd3f66d6..b9b1345017 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -39,6 +39,8 @@ public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogu _catalogue = catalogue; dgResults.Visible = false; lbResults.Visible = false; + lblNoResultsFound.Visible = false; + } @@ -95,9 +97,10 @@ private void handleClick(object sender, DataGridViewCellEventArgs e) private void ShowResults() { if(_results.Rows.Count ==0) { - //TODO show a 'no results found' message + lblNoResultsFound.Visible = true; return; } + lblNoResultsFound.Visible = false; dgResults.DataSource = _results; DataGridViewButtonColumn confirmColumn = new DataGridViewButtonColumn(); confirmColumn.Text = "Redact"; @@ -123,6 +126,7 @@ private void FindCHIs(object sender, EventArgs e) { dgResults.Visible = false; lbResults.Visible = false; + lblNoResultsFound.Visible = false; if (this.cbDoRedaction.Checked && _activator.YesNo("Are you sure you want to blindly redact?", "Redact Catalogue")) { var cmd = new ExecuteCommandRedactCHIsFromCatalogue(_activator, _catalogue, tbAllowList.Text is not null ? tbAllowList.Text : ""); diff --git a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx deleted file mode 100644 index 6252b1ef07..0000000000 --- a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - True - - \ No newline at end of file From 6919c17c8ac8eb6866b117b513c9d9dbebe6e3be Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 15 Dec 2023 14:34:01 +0000 Subject: [PATCH 50/64] tidy up from codeql --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 34 +- .../ExecuteCommandRedactCHIsFromCatalogue.cs | 28 +- .../ExecuteCommandRevertRedactedCHI.cs | 17 +- ...ommandPasteClipboardAsNewCatalogueItems.cs | 4 +- Rdmp.Core/Curation/Data/IRedactedCHI.cs | 10 +- Rdmp.Core/Curation/Data/RedactedCHI.cs | 15 +- Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs | 2 +- .../Runtime/ExecuteCHIRedactionStage.cs | 25 +- .../Mutilators/CHIRedactionMutilator.cs | 5 - Rdmp.Core/Rdmp.Core.csproj | 732 +++++++++--------- .../Secondary/DoesNotContainCHIConstraint.cs | 13 +- .../ExecuteCommandLinkCatalogueToDatasetUI.cs | 4 +- .../ExecuteCommandRedactCHIsInCatalogue.cs | 9 +- ...ecuteCommandViewRedactedCHIsInCatalogue.cs | 9 +- .../RedactChisInCatalogueDialog.cs | 16 +- ...wRedactedCHIsInCatalogueDialog.Designer.cs | 2 +- .../ViewRedactedCHIsInCatalogueDialog.cs | 24 +- 17 files changed, 438 insertions(+), 511 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 26d8c23d24..5369f23742 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -22,6 +22,17 @@ public class ExecuteCommandIdentifyCHIInCatalogue : BasicCommandExecution, IAtom private readonly bool _bailOutEarly; private readonly Dictionary> _allowLists = new(); + private readonly string PotentialCHI = "Potential CHI"; + private readonly string Context = "Context"; + private readonly string SourceColumnName = "Source Column Name"; + private readonly string PKValue = "PK Value"; + private readonly string PKColumn = "PK Column"; + private readonly string ReplacementIndex = "replacementIndex"; + private readonly string RDMP_ALL = "RDMP_ALL"; + + public DataTable foundChis = new(); + + public ExecuteCommandIdentifyCHIInCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, bool bailOutEarly = false, string allowListLocation = null) : base(activator) { _catalouge = catalogue; @@ -51,33 +62,12 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 return $"{source[Math.Max(0, foundIndex - padding)..foundIndex]}{chi}{source[(foundIndex + chi.Length)..Math.Min(foundIndex + chi.Length + padding, source.Length)]}"; } - - private readonly string PotentialCHI = "Potential CHI"; - private readonly string Context = "Context"; - private readonly string SourceColumnName = "Source Column Name"; - private readonly string PKValue = "PK Value"; - private readonly string PKColumn = "PK Column"; - private readonly string ReplacementIndex = "replacementIndex"; - private readonly string RDMP_ALL = "RDMP_ALL"; - - private void HandleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) { if (pkColumn.Split(".").Last().Replace("[", "").Replace("]", "") == columnName.Replace("[", "").Replace("]", "")) return; //don't redact PKs, it gets messy - //if (foundChis.Rows.Count == 0) - //{ - // // init - // foundChis.Columns.Add(PotentialCHI); - // foundChis.Columns.Add(Context); - // foundChis.Columns.Add(SourceColumnName); - // foundChis.Columns.Add(PKValue); - // foundChis.Columns.Add(PKColumn); - // foundChis.Columns.Add(ReplacementIndex); - //} var shrunkContext = WrapCHIInContext(foundChi, contextValue); foundChis.Rows.Add(foundChi, shrunkContext, columnName, pkValue, pkColumn, contextValue.IndexOf(foundChi)); } - public DataTable foundChis = new(); public override void Execute() { @@ -102,7 +92,6 @@ public override void Execute() int idxOfLastSplit = column.LastIndexOf('.'); var columnName = column[(idxOfLastSplit + 1)..]; var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); - // what if there is no pk? var pkColumns = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey); if (pkColumns.Any()) { @@ -133,6 +122,7 @@ public override void Execute() } } } + dt.Dispose(); } else { diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index 2bbbc1968c..ec3bc719e0 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -3,23 +3,11 @@ // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . -using Rdmp.Core.CommandExecution.AtomicCommands; -using Rdmp.Core.CommandExecution; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Rdmp.Core.Curation.Data; -using YamlDotNet.Serialization; -using System.IO; using Rdmp.Core.ReusableLibraryCode.DataAccess; using System.Data; -using static NPOI.HSSF.Util.HSSFColor; -using Rdmp.Core.Curation.Data.Defaults; -using TB.ComponentModel; -using FAnsi.Discovery.TableCreation; -using Rdmp.Core.DataFlowPipeline; using Microsoft.Data.SqlClient; namespace Rdmp.Core.CommandExecution.AtomicCommands; @@ -27,11 +15,10 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandRedactCHIsFromCatalogue : BasicCommandExecution, IAtomicCommand { - private ICatalogue _catalogue; - private IBasicActivateItems _activator; - private readonly Dictionary> _allowLists = new(); + private readonly ICatalogue _catalogue; + private readonly IBasicActivateItems _activator; public int redactionCount = 0; - private string _allowListLocation = ""; + private readonly string _allowListLocation = ""; public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, string allowListLocation = null) : base(activator) { @@ -51,7 +38,6 @@ public override void Execute() redactionCount++; var result = row; var foundChi = result.ItemArray[0].ToString(); - var columnValue = result.ItemArray[1].ToString(); var column = result.ItemArray[2].ToString(); var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; @@ -70,8 +56,11 @@ public override void Execute() using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) { con.Open(); - var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); + var sqlCommand = new SqlCommand(fetchSQL, con); + var da = new SqlDataAdapter(sqlCommand); da.Fill(existingResultsDT); + da.Dispose(); + sqlCommand.Dispose(); if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) { var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); @@ -79,8 +68,11 @@ public override void Execute() var updateSQL = $"update {table} set {column}='{newContext}' where {pkColumn} = '{pkValue}' and {column}='{currentContext}'"; var updateCmd = new SqlCommand(updateSQL, con); updateCmd.ExecuteNonQuery(); + updateCmd.Dispose(); } } + existingResultsDT.Dispose(); } + results.Dispose(); } } diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 06c0d6046d..1129734aa2 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -6,19 +6,15 @@ using Microsoft.Data.SqlClient; using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.DataAccess; -using System; -using System.Collections.Generic; using System.Data; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rdmp.Core.CommandExecution.AtomicCommands; public class ExecuteCommandRevertRedactedCHI : BasicCommandExecution, IAtomicCommand { - RedactedCHI _redactedCHI; - IBasicActivateItems _activator; + private readonly RedactedCHI _redactedCHI; + private readonly IBasicActivateItems _activator; public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsInitialization("Redacted CHI to Revert")] RedactedCHI redaction) : base(activator) { @@ -29,6 +25,7 @@ public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsIn public override void Execute() { base.Execute(); + //todo i think there is some duplication here with other files, may be able to use a helper var redactedString = new string('#', _redactedCHI.PotentialCHI.Length); var fetchSQL = $"select {_redactedCHI.ColumnName} from {_redactedCHI.TableName} where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and charindex('{redactedString}',{_redactedCHI.ColumnName},{_redactedCHI.ReplacementIndex}) >0"; var existingResultsDT = new DataTable(); @@ -37,8 +34,11 @@ public override void Execute() using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) { con.Open(); - var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); + var sqlCommand = new SqlCommand(fetchSQL, con); + var da = new SqlDataAdapter(sqlCommand); da.Fill(existingResultsDT); + sqlCommand.Dispose(); + da.Dispose(); if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) { var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); @@ -46,9 +46,10 @@ public override void Execute() var updateSQL = $"update {_redactedCHI.TableName} set {_redactedCHI.ColumnName}='{newContext}' where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and {_redactedCHI.ColumnName}='{currentContext}'"; var updateCmd = new SqlCommand(updateSQL, con); updateCmd.ExecuteNonQuery(); + updateCmd.Dispose(); } _redactedCHI.DeleteInDatabase(); - } + existingResultsDT.Dispose(); } } diff --git a/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs b/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs index 0e49bcc293..f79ffa7576 100644 --- a/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs +++ b/Rdmp.Core/CommandExecution/ExecuteCommandPasteClipboardAsNewCatalogueItems.cs @@ -45,8 +45,8 @@ public ExecuteCommandPasteClipboardAsNewCatalogueItems(IBasicActivateItems activ _clipboardContentGetter = clipboardContentGetter; } - //public override Image GetImage(IIconProvider iconProvider) => - // iconProvider.GetImage(RDMPConcept.Clipboard, OverlayKind.Import); + public override Image GetImage(IIconProvider iconProvider) => + iconProvider.GetImage(RDMPConcept.Clipboard, OverlayKind.Import); public override void Execute() { diff --git a/Rdmp.Core/Curation/Data/IRedactedCHI.cs b/Rdmp.Core/Curation/Data/IRedactedCHI.cs index 300f2fc0ca..cd3f41ffb5 100644 --- a/Rdmp.Core/Curation/Data/IRedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/IRedactedCHI.cs @@ -3,21 +3,13 @@ // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . -using FAnsi.Naming; -using Rdmp.Core.Curation.Data.Cohort; using Rdmp.Core.MapsDirectlyToDatabaseTable; using Rdmp.Core.Repositories; -using Rdmp.Core.ReusableLibraryCode.Checks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rdmp.Core.Curation.Data; /// -/// This class stores the redacted CHIs that are found during data load of via catalogue mutilation +/// This class stores the redacted CHIs that are found during data load or via catalogue mutilation /// public interface IRedactedCHI : IMapsDirectlyToDatabaseTable { diff --git a/Rdmp.Core/Curation/Data/RedactedCHI.cs b/Rdmp.Core/Curation/Data/RedactedCHI.cs index 6983c1091f..5aee3f85d6 100644 --- a/Rdmp.Core/Curation/Data/RedactedCHI.cs +++ b/Rdmp.Core/Curation/Data/RedactedCHI.cs @@ -3,21 +3,10 @@ // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; -using Rdmp.Core.Icons.IconProvision; -using Rdmp.Core.MapsDirectlyToDatabaseTable.Attributes; using Rdmp.Core.Repositories; -using Rdmp.Core.ReusableLibraryCode.Icons.IconProvision; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp; -using System; using System.Collections.Generic; using System.Data.Common; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Rdmp.Core.MapsDirectlyToDatabaseTable; using System.Diagnostics.CodeAnalysis; -using Rdmp.Core.Curation.Data; @@ -80,7 +69,7 @@ public RedactedCHI(ICatalogueRepository catalogueRepository, string potentialCHI { catalogueRepository.InsertAndHydrate(this, new Dictionary { - {"potentialCHI", potentialCHI },{"replacementIndex",replacementIndex},{"tableName",table},{"PKValue",pkValue},{"pkColumnName",pkColumnName},{"columnName",columnName} + {"PotentialCHI", potentialCHI },{"ReplacementIndex",replacementIndex},{"TableName",table},{"PKValue",pkValue},{"PKColumnName",pkColumnName},{"ColumnName",columnName} }); } @@ -88,7 +77,7 @@ public RedactedCHI() { } public RedactedCHI(ICatalogueRepository repository, DbDataReader r) : base(repository, r) { - PotentialCHI = r["PotentialChi"].ToString(); + PotentialCHI = r["PotentialCHI"].ToString(); ReplacementIndex = int.Parse(r["ReplacementIndex"].ToString()); TableName = r["TableName"].ToString(); PKColumnName = r["PKColumnName"].ToString(); diff --git a/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs b/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs index 8a2f5eced9..b49c6a7b3a 100644 --- a/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs +++ b/Rdmp.Core/DataFlowPipeline/CHIColumnFinder.cs @@ -90,7 +90,7 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener var listFile = new Lazy(() => { var stream = fileLocation is not null ? File.AppendText(fileLocation) : null; - if (stream?.BaseStream.Length == 0) + if (stream?.BaseStream.Length == 0 && _csvColumns is not null) stream.WriteLine(_csvColumns); return stream; }, diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 256e3d43f2..d9151d0463 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -4,8 +4,6 @@ // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; using FAnsi.Discovery; -using Rdmp.Core.CommandExecution; -using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.DataLoad; using Rdmp.Core.DataFlowPipeline; @@ -14,10 +12,7 @@ using System; using System.Collections.Generic; using System.Data; -using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rdmp.Core.DataLoad.Engine.LoadExecution.Components.Runtime; @@ -65,7 +60,6 @@ private void RedactCHIs(ITableInfo tableInfo) List _allowList = new(); if (_allowLists is not null && _allowLists.TryGetValue("RDMP_ALL", out var _extractionSpecificAllowances)) _allowList.AddRange(_extractionSpecificAllowances); - var y = tbl.ToString(); if (_allowLists is not null && _allowLists.TryGetValue(tbl.ToString(), out var _catalogueSpecificAllowances)) _allowList.AddRange(_catalogueSpecificAllowances.ToList()); foreach (DataColumn col in dt.Columns) @@ -81,16 +75,16 @@ private void RedactCHIs(ITableInfo tableInfo) var replacementIdex = row[col].ToString().IndexOf(foundChi); var foundTable = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "").Split("..")[1].Replace("[", "").Replace("]", ""); var catalogue = _job.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(catalogue => catalogue.Name == foundTable).First(); - var pkValue = "fake"; + var pkValue = "Unknown"; var pkColumnName = "Unknown"; if (catalogue != null) { //this can probably be tidied up - Console.WriteLine(foundTable); + //is likely code duplication with another file var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey && x.Name.Contains(foundTable)).First(); //there may be more, but we just need one - pkColumnName = pkColumnInfo.Name; if (pkColumnInfo != null) { + pkColumnName = pkColumnInfo.Name; var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); var arrayNames = (from DataColumn x in dt.Columns.Cast() @@ -99,15 +93,22 @@ in dt.Columns.Cast() pkValue = row[index].ToString(); } } - var ft = tbl.GetFullyQualifiedName().Replace("_RAW", ""); //TODO + var ft = tbl.GetFullyQualifiedName(); + if(_loadStage == LoadStage.AdjustRaw) + { + ft = ft.Replace("_RAW", ""); + } + if (_loadStage == LoadStage.AdjustStaging) + { + ft = ft.Replace("_STAGING", ""); + } ft = ft.Replace("..", ".[dbo]."); if (pkColumnName.Split(".").Last().Replace("[","").Replace("]","") != $"{col.ColumnName}") { - Console.WriteLine("hi"); var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); rc.SaveToDatabase(); var redactionString = "##########"; - row[col] = row[col].ToString().Replace(foundChi, redactionString.Substring(0, Math.Min(foundChi.Length, redactionString.Length))); + row[col] = row[col].ToString().Replace(foundChi, redactionString[..Math.Min(foundChi.Length, redactionString.Length)]); } } else diff --git a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs index d653af07e8..04759e0ae0 100644 --- a/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs +++ b/Rdmp.Core/DataLoad/Modules/Mutilators/CHIRedactionMutilator.cs @@ -11,13 +11,8 @@ using Rdmp.Core.DataLoad.Engine.Mutilators; using Rdmp.Core.ReusableLibraryCode.Checks; using Rdmp.Core.ReusableLibraryCode.Progress; -using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using YamlDotNet.Serialization; namespace Rdmp.Core.DataLoad.Modules.Mutilators; diff --git a/Rdmp.Core/Rdmp.Core.csproj b/Rdmp.Core/Rdmp.Core.csproj index bfee766678..a57e37046c 100644 --- a/Rdmp.Core/Rdmp.Core.csproj +++ b/Rdmp.Core/Rdmp.Core.csproj @@ -15,370 +15,370 @@ Core package for plugin development Copyright 2018-2019 - net7.0 - false - true - net7.0 - true - 1701;1702;CS1591;SCS0018 - embedded - true - $(NoWarn);NU5104 - - - Rdmp.Core.DataLoad.Modules.DataProvider.WebServiceConfiguration - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Never - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - Never - - - - - - - - - True - True - GlobalStrings.resx - - - True - True - ChecksAndProgressIcons.resx - - - True - True - Images.resx - - - DatabaseProviderIcons.resx - True - True - - - - - PublicResXFileCodeGenerator - GlobalStrings.Designer.cs - - - PublicResXFileCodeGenerator - - - - - - PublicResXFileCodeGenerator - ChecksAndProgressIcons.Designer.cs - - - Designer - PublicResXFileCodeGenerator - Images.Designer.cs - - - Designer - DatabaseProviderIcons.Designer.cs - PublicResXFileCodeGenerator - - + net7.0 + false + true + net7.0 + true + 1701;1702;CS1591;SCS0018 + embedded + true + $(NoWarn);NU5104 + + + Rdmp.Core.DataLoad.Modules.DataProvider.WebServiceConfiguration + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Never + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + Never + + + + + + + + + True + True + GlobalStrings.resx + + + True + True + ChecksAndProgressIcons.resx + + + True + True + Images.resx + + + DatabaseProviderIcons.resx + True + True + + + + + PublicResXFileCodeGenerator + GlobalStrings.Designer.cs + + + PublicResXFileCodeGenerator + + + + + + PublicResXFileCodeGenerator + ChecksAndProgressIcons.Designer.cs + + + Designer + PublicResXFileCodeGenerator + Images.Designer.cs + + + Designer + DatabaseProviderIcons.Designer.cs + PublicResXFileCodeGenerator + + diff --git a/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs b/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs index 73cfa59b1f..dbe6bf2677 100644 --- a/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs +++ b/Rdmp.Core/Validation/Constraints/Secondary/DoesNotContainCHIConstraint.cs @@ -15,14 +15,13 @@ namespace Rdmp.Core.Validation.Constraints.Secondary; /// -/// TODO +/// Secondary constraint that looks for potential CHI values in the data /// public class DoesNotContainCHIConstraint : SecondaryConstraint, ICheckable { - private readonly IRepository _repository; - public DoesNotContainCHIConstraint() { - _repository = Validator.LocatorForXMLDeserialization.CatalogueRepository; + public DoesNotContainCHIConstraint() + { } @@ -32,7 +31,7 @@ public void Check(ICheckNotifier notifier) public override string GetHumanReadableDescriptionOfValidation() { - return "TODO"; + return "Finds potential CHI values in the data"; } public override void RenameColumn(string originalName, string newName) @@ -47,7 +46,7 @@ public override ValidationFailure Validate(object value, object[] otherColumns, if (string.IsNullOrWhiteSpace(value.ToString())) return null; var potentialCHI = CHIColumnFinder.GetPotentialCHI(value.ToString()); - if(string.IsNullOrWhiteSpace(potentialCHI)) return null; - return new ValidationFailure($"Potential CHI {potentialCHI} was found.",this); + if (string.IsNullOrWhiteSpace(potentialCHI)) return null; + return new ValidationFailure($"Potential CHI {potentialCHI} was found.", this); } } diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs index 00db3270ce..d0b99eb69d 100644 --- a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandLinkCatalogueToDatasetUI.cs @@ -49,6 +49,6 @@ public override void Execute() cmd.Execute(); } - //public override Image GetImage([NotNull] IIconProvider iconProvider) => - // iconProvider.GetImage(RDMPConcept.Dataset, OverlayKind.Link); + public override Image GetImage([NotNull] IIconProvider iconProvider) => + iconProvider.GetImage(RDMPConcept.Dataset, OverlayKind.Link); } \ No newline at end of file diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs index 1e47d600ff..c909f16d51 100644 --- a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsInCatalogue.cs @@ -6,18 +6,13 @@ using Rdmp.Core.Curation.Data; using Rdmp.UI.ItemActivation; using Rdmp.UI.SimpleDialogs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rdmp.UI.CommandExecution.AtomicCommands; public class ExecuteCommandRedactCHIsInCatalogue: BasicUICommandExecution { - private ICatalogue _catalogue; - private IActivateItems _activator; + private readonly ICatalogue _catalogue; + private readonly IActivateItems _activator; public ExecuteCommandRedactCHIsInCatalogue(IActivateItems activator, ICatalogue catalogue) : base(activator) { _catalogue = catalogue; diff --git a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs index 19f0b52bea..b30738f8e1 100644 --- a/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs +++ b/Rdmp.UI/CommandExecution/AtomicCommands/ExecuteCommandViewRedactedCHIsInCatalogue.cs @@ -7,18 +7,13 @@ using Rdmp.Core.Curation.Data; using Rdmp.UI.ItemActivation; using Rdmp.UI.SimpleDialogs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rdmp.UI.CommandExecution.AtomicCommands; public class ExecuteCommandViewRedactedCHIsInCatalogue : BasicUICommandExecution { - private ICatalogue _catalogue; - private IActivateItems _activator; + private readonly ICatalogue _catalogue; + private readonly IActivateItems _activator; public ExecuteCommandViewRedactedCHIsInCatalogue(IActivateItems activator, ICatalogue catalogue) : base(activator) { _catalogue = catalogue; diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index b9b1345017..4c54922e10 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -8,27 +8,18 @@ using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.DataAccess; using Rdmp.UI.ItemActivation; -using Rdmp.UI.MainFormUITabs; using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Data; -using System.Drawing; -using System.IO; using System.Linq; -using System.Runtime.ConstrainedExecution; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; -using YamlDotNet.Serialization; namespace Rdmp.UI.SimpleDialogs { public partial class RedactChisInCatalogueDialog : Form { - private IActivateItems _activator; - private ICatalogue _catalogue; + private readonly IActivateItems _activator; + private readonly ICatalogue _catalogue; private DataTable _results; private bool _firstTime = true; @@ -46,9 +37,9 @@ public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogu private void Redact(int rowIndex) { + //todo this whole function is duped elsehwere, join them up var result = _results.Rows[rowIndex]; var foundChi = result.ItemArray[0].ToString(); - var columnValue = result.ItemArray[1].ToString(); var column = result.ItemArray[2].ToString(); var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); var name = catalogueItem.ColumnInfo.Name; @@ -111,6 +102,7 @@ private void ShowResults() { dgResults.Columns.Insert(3, confirmColumn); } + confirmColumn.Dispose(); if (_firstTime) { dgResults.Columns[4].Visible = false; diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs index 3b45c33571..604d09179a 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.Designer.cs @@ -44,7 +44,7 @@ private void InitializeComponent() btnSearch.TabIndex = 0; btnSearch.Text = "Search"; btnSearch.UseVisualStyleBackColor = true; - btnSearch.Click += searchButtonClick; + btnSearch.Click += SearchButtonClick; // // dtResults // diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index e9a4b96196..2f903cffa7 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -8,12 +8,8 @@ using Rdmp.Core.Curation.Data; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Data; -using System.Drawing; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; namespace Rdmp.UI.SimpleDialogs @@ -22,8 +18,8 @@ public partial class ViewRedactedCHIsInCatalogueDialog : Form { private bool _isLoading = true; private bool _firstTime = true; - private IBasicActivateItems _activator; - private ICatalogue _catalogue; + private readonly IBasicActivateItems _activator; + private readonly ICatalogue _catalogue; private DataTable _results; @@ -53,11 +49,6 @@ private void RevertButtonClick(int itemIndex) result.Delete(); _results.AcceptChanges(); dtResults.DataSource = _results; - //dtResults.Columns[5].Visible = false; - //dtResults.Columns[5].Visible = false; - //dtResults.Columns[5].Visible = false; - //dtResults.Columns[5].Visible = false;//todo this isn't quite right - } } @@ -77,14 +68,10 @@ private void ConfirmButtonClick(int itemIndex) result.Delete(); _results.AcceptChanges(); dtResults.DataSource = _results; - //dtResults.Columns[5].Visible = false; - - //dtResults.Columns[5].Visible = false;//todo this isn't quite right - } } - private void handleClick(object sender, DataGridViewCellEventArgs e) + private void HandleClick(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex == dtResults.Columns["Revert"].Index) { @@ -147,7 +134,6 @@ private void FindChis() dtResults.Columns[4].Visible = false; _firstTime = false; } - //dtResults.Columns[3].Visible = false; _results = dt; DataGridViewButtonColumn revertColumn = new DataGridViewButtonColumn(); revertColumn.Text = "Revert"; @@ -158,7 +144,7 @@ private void FindChis() confirmColumn.Text = "Confirm"; confirmColumn.Name = "Confirm"; confirmColumn.UseColumnTextForButtonValue = true; - dtResults.CellClick += handleClick; + dtResults.CellClick += HandleClick; if (dtResults.Columns["Revert"] == null) { dtResults.Columns.Insert(3, revertColumn); @@ -173,7 +159,7 @@ private void FindChis() } - private void searchButtonClick(object sender, EventArgs e) + private void SearchButtonClick(object sender, EventArgs e) { _isLoading = true; lblLoading.Visible = _isLoading; From 707bf8eaa9992e724614e56563c6d54165bb663a Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 15 Dec 2023 15:48:21 +0000 Subject: [PATCH 51/64] add missing file --- Rdmp.UI/SimpleDialogs/SelectDialog`1.resx | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Rdmp.UI/SimpleDialogs/SelectDialog`1.resx diff --git a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx new file mode 100644 index 0000000000..6252b1ef07 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + True + + \ No newline at end of file From 2c36e42d0227fb7fbdc04e71b888f65bb0a378bc Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 09:20:18 +0000 Subject: [PATCH 52/64] fix csproj --- Rdmp.Core/Rdmp.Core.csproj | 374 +------------------------------------ 1 file changed, 3 insertions(+), 371 deletions(-) diff --git a/Rdmp.Core/Rdmp.Core.csproj b/Rdmp.Core/Rdmp.Core.csproj index 2feb3b1c0a..eeebea3fd4 100644 --- a/Rdmp.Core/Rdmp.Core.csproj +++ b/Rdmp.Core/Rdmp.Core.csproj @@ -15,7 +15,6 @@ Core package for plugin development Copyright 2018-2019 -<<<<<<< HEAD net7.0 false true @@ -123,7 +122,7 @@ - +07 @@ -310,7 +309,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -382,371 +381,4 @@ PublicResXFileCodeGenerator - -======= - net7.0 - false - true - net7.0 - true - 1701;1702;CS1591;SCS0018 - embedded - true - $(NoWarn);NU5104 - - - Rdmp.Core.DataLoad.Modules.DataProvider.WebServiceConfiguration - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Never - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - Never - - - - - - - - - True - True - GlobalStrings.resx - - - True - True - ChecksAndProgressIcons.resx - - - True - True - Images.resx - - - DatabaseProviderIcons.resx - True - True - - - - - PublicResXFileCodeGenerator - GlobalStrings.Designer.cs - - - PublicResXFileCodeGenerator - - - - - - PublicResXFileCodeGenerator - ChecksAndProgressIcons.Designer.cs - - - Designer - PublicResXFileCodeGenerator - Images.Designer.cs - - - Designer - DatabaseProviderIcons.Designer.cs - PublicResXFileCodeGenerator - - - ->>>>>>> 7dfec9216df0aa4bf1f74c3e238df695d1ff2579 + \ No newline at end of file From d75eddc5cb1cd0413e00154ac1631b5e1f791097 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 09:24:44 +0000 Subject: [PATCH 53/64] fix build --- Rdmp.Core/Rdmp.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rdmp.Core/Rdmp.Core.csproj b/Rdmp.Core/Rdmp.Core.csproj index eeebea3fd4..9e6ebc6156 100644 --- a/Rdmp.Core/Rdmp.Core.csproj +++ b/Rdmp.Core/Rdmp.Core.csproj @@ -122,7 +122,7 @@ -07 + From 886010e14c3c8f20f8df6befdea01704143657db Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 10:23:52 +0000 Subject: [PATCH 54/64] improved multi dataset catalogue --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 3 ++- .../RedactChisInCatalogueDialog.cs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 5369f23742..2a522d68a7 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -93,7 +93,7 @@ public override void Execute() var columnName = column[(idxOfLastSplit + 1)..]; var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); var pkColumns = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey); - if (pkColumns.Any()) + if (pkColumns.Where(pkc => pkc.Name.Contains(columnName)).Any()) { var pkColumn = pkColumns.First().Name.Split(".").Last(); @@ -126,6 +126,7 @@ public override void Execute() } else { + //todo this should probably warn the UI Console.WriteLine("Unable to find a primary key for this catalogue"); } } diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 4c54922e10..7592466b88 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -4,6 +4,7 @@ // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . using Microsoft.Data.SqlClient; +using NLog.LayoutRenderers; using Rdmp.Core.CommandExecution.AtomicCommands; using Rdmp.Core.Curation.Data; using Rdmp.Core.ReusableLibraryCode.DataAccess; @@ -11,6 +12,7 @@ using System; using System.Data; using System.Linq; +using System.Text.RegularExpressions; using System.Windows.Forms; namespace Rdmp.UI.SimpleDialogs @@ -34,6 +36,16 @@ public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogu } + private static string ReplaceLastOccurrence(string source, string find, string replace) + { + int place = source.LastIndexOf(find); + + if (place == -1) + return source; + + return source.Remove(place, find.Length).Insert(place, replace); + } + private void Redact(int rowIndex) { @@ -45,7 +57,10 @@ private void Redact(int rowIndex) var name = catalogueItem.ColumnInfo.Name; var pkValue = result.ItemArray[3].ToString(); var replacementIdex = int.Parse(result.ItemArray[5].ToString()); - var table = name.Replace($".[{column}]", ""); + var table = name; + table = ReplaceLastOccurrence(table, $".[{column}]", ""); + + //var table = Regex.Replace(name,);//name.Replace($".[{column}]", ""); var pkColumn = result.ItemArray[4].ToString().Replace(table,"").Replace(".",""); var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue,pkColumn, $"[{column}]"); rc.SaveToDatabase(); From 1c87027888f549a2713420587c24f43565c0cb9f Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 10:46:53 +0000 Subject: [PATCH 55/64] start to dedupe code --- .../ExecuteCommandRedactCHIsFromCatalogue.cs | 43 +++-------- .../Curation/Data/CHIRedactionHelpers.cs | 71 +++++++++++++++++++ .../RedactChisInCatalogueDialog.cs | 57 +++------------ 3 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs index ec3bc719e0..e189c5a94a 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRedactCHIsFromCatalogue.cs @@ -19,14 +19,16 @@ public class ExecuteCommandRedactCHIsFromCatalogue : BasicCommandExecution, IAto private readonly IBasicActivateItems _activator; public int redactionCount = 0; private readonly string _allowListLocation = ""; + private CHIRedactionHelpers redactionHelper; public ExecuteCommandRedactCHIsFromCatalogue(IBasicActivateItems activator, [DemandsInitialization("The catalogue to search")] ICatalogue catalogue, string allowListLocation = null) : base(activator) { _catalogue = catalogue; _activator = activator; _allowListLocation = allowListLocation; + redactionHelper = new CHIRedactionHelpers(activator, catalogue); } - + public override void Execute() { base.Execute(); @@ -36,42 +38,13 @@ public override void Execute() foreach(DataRow row in results.Rows) { redactionCount++; - var result = row; - var foundChi = result.ItemArray[0].ToString(); - var column = result.ItemArray[2].ToString(); - var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); - var name = catalogueItem.ColumnInfo.Name; - var pkValue = result.ItemArray[3].ToString(); - var replacementIdex = int.Parse(result.ItemArray[5].ToString()); - var table = name.Replace($".[{column}]", ""); - var pkColumn = result.ItemArray[4].ToString().Replace(table, "").Replace(".", ""); - var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue, pkColumn, $"[{column}]"); - rc.SaveToDatabase(); - - var redactedString = new string('#', foundChi.Length); - var fetchSQL = $"select TOP 1 {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; - var existingResultsDT = new DataTable(); - var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{table}.[{column}]").First(); - var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; - using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + try { - con.Open(); - var sqlCommand = new SqlCommand(fetchSQL, con); - var da = new SqlDataAdapter(sqlCommand); - da.Fill(existingResultsDT); - da.Dispose(); - sqlCommand.Dispose(); - if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) - { - var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); - var newContext = currentContext.Replace(foundChi, redactedString); - var updateSQL = $"update {table} set {column}='{newContext}' where {pkColumn} = '{pkValue}' and {column}='{currentContext}'"; - var updateCmd = new SqlCommand(updateSQL, con); - updateCmd.ExecuteNonQuery(); - updateCmd.Dispose(); - } + redactionHelper.Redact(row); + } + catch { + //todo some sort of warning } - existingResultsDT.Dispose(); } results.Dispose(); } diff --git a/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs b/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs new file mode 100644 index 0000000000..eb30c5fdfe --- /dev/null +++ b/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs @@ -0,0 +1,71 @@ +using Microsoft.Data.SqlClient; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.ReusableLibraryCode.DataAccess; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.Core.Curation.Data; + +public class CHIRedactionHelpers +{ + + private readonly IBasicActivateItems _activator; + private readonly ICatalogue _catalogue; + + + public CHIRedactionHelpers(IBasicActivateItems activator, ICatalogue catalogue) + { + _activator = activator; + _catalogue = catalogue; + } + + public void Redact(DataRow itemToRedact)//[foundChiValue,columnName,pkValue,pkColumnName,replacementIndex] + { + var foundChi = itemToRedact.ItemArray[0].ToString(); + var column = itemToRedact.ItemArray[2].ToString(); + var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); + var name = catalogueItem.ColumnInfo.Name; + var pkValue = itemToRedact.ItemArray[3].ToString(); + var replacementIdex = int.Parse(itemToRedact.ItemArray[5].ToString()); + var table = name; + table = ReplaceLastOccurrence(table, $".[{column}]", ""); + var pkColumn = itemToRedact.ItemArray[4].ToString().Replace(table, "").Replace(".", ""); + var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue, pkColumn, $"[{column}]"); + rc.SaveToDatabase(); + + var redactedString = new string('#', foundChi.Length); + var fetchSQL = $"select TOP 1 {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; + var existingResultsDT = new DataTable(); + var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{table}.[{column}]").First(); + var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; + using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + { + con.Open(); + var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); + da.Fill(existingResultsDT); + if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) + { + var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); + var newContext = currentContext.Replace(foundChi, redactedString); + var updateSQL = $"update {table} set {column}='{newContext}' where {pkColumn} = '{pkValue}' and {column}='{currentContext}'"; + var updateCmd = new SqlCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); + } + } + } + + + private static string ReplaceLastOccurrence(string source, string find, string replace) + { + int place = source.LastIndexOf(find); + + if (place == -1) + return source; + + return source.Remove(place, find.Length).Insert(place, replace); + } +} diff --git a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs index 7592466b88..c8967b69b8 100644 --- a/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/RedactChisInCatalogueDialog.cs @@ -24,6 +24,7 @@ public partial class RedactChisInCatalogueDialog : Form private readonly ICatalogue _catalogue; private DataTable _results; private bool _firstTime = true; + private CHIRedactionHelpers redactionHelper; public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogue) { @@ -33,62 +34,22 @@ public RedactChisInCatalogueDialog(IActivateItems activator, ICatalogue catalogu dgResults.Visible = false; lbResults.Visible = false; lblNoResultsFound.Visible = false; + redactionHelper = new CHIRedactionHelpers(activator, catalogue); } - private static string ReplaceLastOccurrence(string source, string find, string replace) - { - int place = source.LastIndexOf(find); - - if (place == -1) - return source; - - return source.Remove(place, find.Length).Insert(place, replace); - } - private void Redact(int rowIndex) { - //todo this whole function is duped elsehwere, join them up - var result = _results.Rows[rowIndex]; - var foundChi = result.ItemArray[0].ToString(); - var column = result.ItemArray[2].ToString(); - var catalogueItem = _catalogue.CatalogueItems.Where(ci => ci.Name == column).First(); - var name = catalogueItem.ColumnInfo.Name; - var pkValue = result.ItemArray[3].ToString(); - var replacementIdex = int.Parse(result.ItemArray[5].ToString()); - var table = name; - table = ReplaceLastOccurrence(table, $".[{column}]", ""); - - //var table = Regex.Replace(name,);//name.Replace($".[{column}]", ""); - var pkColumn = result.ItemArray[4].ToString().Replace(table,"").Replace(".",""); - var rc = new RedactedCHI(_catalogue.CatalogueRepository, foundChi, replacementIdex, table, pkValue,pkColumn, $"[{column}]"); - rc.SaveToDatabase(); - - var redactedString = new string('#', foundChi.Length); - var fetchSQL = $"select TOP 1 {column} from {table} where {pkColumn} = '{pkValue}' and charindex('{foundChi}',{column},{replacementIdex}) >0"; - var existingResultsDT = new DataTable(); - var columnInfo = _activator.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(ci => ci.Name == $"{table}.[{column}]").First(); - var catalogue = columnInfo.CatalogueItems.FirstOrDefault().Catalogue; - using (var con = (SqlConnection)catalogue.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false).GetConnection()) + try { - con.Open(); - var da = new SqlDataAdapter(new SqlCommand(fetchSQL, con)); - da.Fill(existingResultsDT); - if (existingResultsDT.Rows.Count > 0 && existingResultsDT.Rows[0].ItemArray.Length > 0) - { - var currentContext = existingResultsDT.Rows[0].ItemArray[0].ToString(); - var newContext = currentContext.Replace(foundChi, redactedString); - var updateSQL = $"update {table} set {column}='{newContext}' where {pkColumn} = '{pkValue}' and {column}='{currentContext}'"; - var updateCmd = new SqlCommand(updateSQL, con); - updateCmd.ExecuteNonQuery(); - } + redactionHelper.Redact(_results.Rows[rowIndex]); + _results.Rows[rowIndex].Delete(); + _results.AcceptChanges(); + dgResults.DataSource = _results; + } catch { + //todo some warning about it having not worked } - - - result.Delete(); - _results.AcceptChanges(); - dgResults.DataSource = _results; } private void handleClick(object sender, DataGridViewCellEventArgs e) From 40c621861058fbb8e655a47aaa128c4c56add06a Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 11:05:40 +0000 Subject: [PATCH 56/64] add summaries --- .../ExecuteCommandIdentifyCHIInCatalogue.cs | 9 ++++---- .../Curation/Data/CHIRedactionHelpers.cs | 22 ++++++++++++++++++- .../Runtime/ExecuteCHIRedactionStage.cs | 5 +++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 2a522d68a7..8eed03f2a1 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -29,6 +29,7 @@ public class ExecuteCommandIdentifyCHIInCatalogue : BasicCommandExecution, IAtom private readonly string PKColumn = "PK Column"; private readonly string ReplacementIndex = "replacementIndex"; private readonly string RDMP_ALL = "RDMP_ALL"; + private CHIRedactionHelpers redactionHelper = new CHIRedactionHelpers(null, null); public DataTable foundChis = new(); @@ -64,7 +65,7 @@ public static string WrapCHIInContext(string chi, string source, int padding = 2 private void HandleFoundCHI(string foundChi, string contextValue, string columnName, string pkValue, string pkColumn) { - if (pkColumn.Split(".").Last().Replace("[", "").Replace("]", "") == columnName.Replace("[", "").Replace("]", "")) return; //don't redact PKs, it gets messy + if (redactionHelper.GetColumnNameFromColumnInfoName(pkColumn) == redactionHelper.StripEnclosingBrackets(columnName)) return; //don't redact PKs, it gets messy var shrunkContext = WrapCHIInContext(foundChi, contextValue); foundChis.Rows.Add(foundChi, shrunkContext, columnName, pkValue, pkColumn, contextValue.IndexOf(foundChi)); } @@ -138,13 +139,11 @@ public override void Execute() } } - private static string GetPKValue(ColumnInfo pkColumnInfo, DataRow row, DataTable dt) + private string GetPKValue(ColumnInfo pkColumnInfo, DataRow row, DataTable dt) { - //todo doesn't work with multitable catalogues - if (pkColumnInfo != null) { - var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); + var pkName = redactionHelper.GetColumnNameFromColumnInfoName(pkColumnInfo.Name); var arrayNames = (from DataColumn x in dt.Columns.Cast() select x.ColumnName).ToList(); diff --git a/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs b/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs index eb30c5fdfe..d090a611df 100644 --- a/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs +++ b/Rdmp.Core/Curation/Data/CHIRedactionHelpers.cs @@ -1,4 +1,9 @@ -using Microsoft.Data.SqlClient; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see .using Amazon.Auth.AccessControlPolicy; +using Microsoft.Data.SqlClient; using Rdmp.Core.CommandExecution; using Rdmp.Core.ReusableLibraryCode.DataAccess; using System; @@ -10,6 +15,10 @@ namespace Rdmp.Core.Curation.Data; + +/// +/// Contains several reusalbe functions and utilites for dealing with redacting CHIs from catalogues +/// public class CHIRedactionHelpers { @@ -59,6 +68,17 @@ public void Redact(DataRow itemToRedact)//[foundChiValue,columnName,pkValue,pkCo } + public string GetColumnNameFromColumnInfoName(string columnInfoName) + { + return columnInfoName.Split(".").Last().Replace("[", "").Replace("]", ""); + } + + public string StripEnclosingBrackets(string stringWithEnclosingBrackets) + { + return stringWithEnclosingBrackets.Replace("[", "").Replace("]", ""); + } + + private static string ReplaceLastOccurrence(string source, string find, string replace) { int place = source.LastIndexOf(find); diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index d9151d0463..f80f9634b7 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -23,6 +23,7 @@ internal class ExecuteCHIRedactionStage private readonly LoadStage _loadStage; private Dictionary> _allowLists = new(); private bool _redact; + private CHIRedactionHelpers _chiRedactionHelper = new CHIRedactionHelpers(null,null); public ExecuteCHIRedactionStage(IDataLoadJob job, DiscoveredDatabase db, LoadStage loadStage) { @@ -85,7 +86,7 @@ private void RedactCHIs(ITableInfo tableInfo) if (pkColumnInfo != null) { pkColumnName = pkColumnInfo.Name; - var pkName = pkColumnInfo.Name.Split(".").Last().Replace("[", "").Replace("]", ""); + var pkName = _chiRedactionHelper.GetColumnNameFromColumnInfoName(pkColumnInfo.Name); var arrayNames = (from DataColumn x in dt.Columns.Cast() select x.ColumnName).ToList(); @@ -103,7 +104,7 @@ in dt.Columns.Cast() ft = ft.Replace("_STAGING", ""); } ft = ft.Replace("..", ".[dbo]."); - if (pkColumnName.Split(".").Last().Replace("[","").Replace("]","") != $"{col.ColumnName}") + if (_chiRedactionHelper.GetColumnNameFromColumnInfoName(pkColumnName) != $"{col.ColumnName}") { var rc = new RedactedCHI(_job.RepositoryLocator.CatalogueRepository, foundChi, replacementIdex, ft, pkValue, pkColumnName.Split(".").Last(), $"[{col.ColumnName}]"); rc.SaveToDatabase(); From bae18fb3a535ae2c25f44bc33bbb5eafe529357b Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 11:21:37 +0000 Subject: [PATCH 57/64] tidy up code --- .../AtomicCommands/ExecuteCommandRevertRedactedCHI.cs | 1 - .../Components/Runtime/ExecuteCHIRedactionStage.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs index 1129734aa2..00da8553ba 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandRevertRedactedCHI.cs @@ -25,7 +25,6 @@ public ExecuteCommandRevertRedactedCHI(IBasicActivateItems activator, [DemandsIn public override void Execute() { base.Execute(); - //todo i think there is some duplication here with other files, may be able to use a helper var redactedString = new string('#', _redactedCHI.PotentialCHI.Length); var fetchSQL = $"select {_redactedCHI.ColumnName} from {_redactedCHI.TableName} where {_redactedCHI.PKColumnName} = '{_redactedCHI.PKValue}' and charindex('{redactedString}',{_redactedCHI.ColumnName},{_redactedCHI.ReplacementIndex}) >0"; var existingResultsDT = new DataTable(); diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index f80f9634b7..0c8ba0e481 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -80,8 +80,6 @@ private void RedactCHIs(ITableInfo tableInfo) var pkColumnName = "Unknown"; if (catalogue != null) { - //this can probably be tidied up - //is likely code duplication with another file var pkColumnInfo = catalogue.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey && x.Name.Contains(foundTable)).First(); //there may be more, but we just need one if (pkColumnInfo != null) { From 196e83c7a727550e414de0cb4feb6349c3d6d59d Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 11:27:52 +0000 Subject: [PATCH 58/64] add missed elper useage --- .../Components/Runtime/ExecuteCHIRedactionStage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index 0c8ba0e481..fd15aa90f7 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -74,7 +74,7 @@ private void RedactCHIs(ITableInfo tableInfo) if (_redact) { var replacementIdex = row[col].ToString().IndexOf(foundChi); - var foundTable = tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "").Split("..")[1].Replace("[", "").Replace("]", ""); + var foundTable = _chiRedactionHelper.StripEnclosingBrackets(tbl.GetFullyQualifiedName().Replace(_loadStage.ToString(), "").Split("..")[1]); var catalogue = _job.RepositoryLocator.CatalogueRepository.GetAllObjects().Where(catalogue => catalogue.Name == foundTable).First(); var pkValue = "Unknown"; var pkColumnName = "Unknown"; From d1cb089f3edfad26db2a23938cf438c41737d826 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 13:17:02 +0000 Subject: [PATCH 59/64] fix overcorrection --- .../CommandExecution/ExecuteCommandRevertRedactedCHITests.cs | 4 ++++ Rdmp.Core.Tests/Rdmp.Core.Tests.csproj | 4 ++-- .../AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index c38e497293..cb05952ade 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -7,6 +7,7 @@ using Rdmp.Core.CommandLine.DatabaseCreation; using Rdmp.Core.ReusableLibraryCode.Checks; using System.Data; +using System; namespace Rdmp.Core.Tests.CommandExecution; @@ -16,6 +17,9 @@ internal class ExecuteCommandRevertRedactedCHITests : DatabaseTests public void RevertRedactedCHI_Valid() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); + var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); + pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); + var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); diff --git a/Rdmp.Core.Tests/Rdmp.Core.Tests.csproj b/Rdmp.Core.Tests/Rdmp.Core.Tests.csproj index aa3f251a8f..920e0629e0 100644 --- a/Rdmp.Core.Tests/Rdmp.Core.Tests.csproj +++ b/Rdmp.Core.Tests/Rdmp.Core.Tests.csproj @@ -83,8 +83,8 @@ - - + + diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs index 8eed03f2a1..e89532efd1 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandIdentifyCHIInCatalogue.cs @@ -92,9 +92,10 @@ public override void Execute() var column = item.ColumnInfo.Name; int idxOfLastSplit = column.LastIndexOf('.'); var columnName = column[(idxOfLastSplit + 1)..]; + var table = column[..(idxOfLastSplit + 1)]; var server = _catalouge.GetDistinctLiveDatabaseServer(DataAccessContext.InternalDataProcessing, false); var pkColumns = _catalouge.CatalogueItems.Select(x => x.ColumnInfo).Where(x => x.IsPrimaryKey); - if (pkColumns.Where(pkc => pkc.Name.Contains(columnName)).Any()) + if (pkColumns.Where(pkc => pkc.Name.Contains(table)).Any()) { var pkColumn = pkColumns.First().Name.Split(".").Last(); From 775bcaf2e3621649ced5d999f2d3c14788f50ad8 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 18 Dec 2023 13:51:37 +0000 Subject: [PATCH 60/64] remove pipes --- .../CommandExecution/ExecuteCommandRevertRedactedCHITests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs index cb05952ade..5f1fa19ec1 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRevertRedactedCHITests.cs @@ -17,9 +17,6 @@ internal class ExecuteCommandRevertRedactedCHITests : DatabaseTests public void RevertRedactedCHI_Valid() { var db = GetCleanedServer(FAnsi.DatabaseType.MicrosoftSQLServer); - var pipes = new CataloguePipelinesAndReferencesCreation(RepositoryLocator, null, null); - pipes.CreatePipelines(new PlatformDatabaseCreationOptions()); - var creator = new ExampleDatasetsCreation(new ThrowImmediatelyActivator(RepositoryLocator), RepositoryLocator); creator.Create(db, ThrowImmediatelyCheckNotifier.Quiet, new PlatformDatabaseCreationOptions { Seed = 500, DropDatabases = true }); From df1f918a9ab85f027b6990d8f6021740a5ddd79c Mon Sep 17 00:00:00 2001 From: James A Sutherland <> Date: Wed, 3 Jan 2024 12:16:39 -0600 Subject: [PATCH 61/64] Dispose to using fixups --- ...ecuteCommandRedactCHIsFromCatalogueTests.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 3a9ce17bd2..5405cb9d90 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -32,17 +32,12 @@ public void RedactCHIsFromCatalogue_ValidSingle() } var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, null); Assert.DoesNotThrow(() => cmd.Execute()); - var dt = new DataTable(); - using (var con = tbl.Database.Server.GetConnection()) - { - using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) - { - using var da = tbl.Database.Server.GetDataAdapter(findCmd); - da.Fill(dt); - } - } + using var dt = new DataTable(); + using var con = tbl.Database.Server.GetConnection(); + using var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con); + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); Assert.That(dt.Rows.Count, Is.EqualTo(2)); - dt.Dispose(); } [Test] @@ -69,7 +64,7 @@ public void RedactCHIsFromCatalogue_Allowlist() File.WriteAllLines(tempFileToCreate, new string[] { "Biochemistry:", "- SampleType" }); var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, tempFileToCreate); Assert.DoesNotThrow(() => cmd.Execute()); - var dt = new DataTable(); + using var dt = new DataTable(); using (var con = tbl.Database.Server.GetConnection()) { using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) @@ -79,6 +74,5 @@ public void RedactCHIsFromCatalogue_Allowlist() } } Assert.That(dt.Rows.Count, Is.EqualTo(0)); - dt.Dispose(); } } From b4e41603151260edb560b0b7a026ed7dea4853fb Mon Sep 17 00:00:00 2001 From: James A Sutherland <> Date: Wed, 3 Jan 2024 12:29:00 -0600 Subject: [PATCH 62/64] Variable scope fix --- ...cuteCommandRedactCHIsFromCatalogueTests.cs | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 5405cb9d90..90f70ef7f5 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -24,16 +24,13 @@ public void RedactCHIsFromCatalogue_ValidSingle() var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = 'F1111111111'"; - using (var con = tbl.Database.Server.GetConnection()) - { - con.Open(); - var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); - updateCmd.ExecuteNonQuery(); - } + using var con = tbl.Database.Server.GetConnection(); + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, null); Assert.DoesNotThrow(() => cmd.Execute()); using var dt = new DataTable(); - using var con = tbl.Database.Server.GetConnection(); using var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con); using var da = tbl.Database.Server.GetDataAdapter(findCmd); da.Fill(dt); @@ -52,12 +49,10 @@ public void RedactCHIsFromCatalogue_Allowlist() var biochemistry = CatalogueRepository.GetAllObjects().Where(c => c.Name == "Biochemistry").First(); var tbl = db.DiscoverTables(true).Where(dt => dt.GetRuntimeName().Contains("Biochemistry")).First(); var updateSQL = $"UPDATE top (2) {tbl.GetRuntimeName()} set SampleType = 'F1111111111'"; - using (var con = tbl.Database.Server.GetConnection()) - { - con.Open(); - var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); - updateCmd.ExecuteNonQuery(); - } + using var con = tbl.Database.Server.GetConnection(); + con.Open(); + var updateCmd = tbl.Database.Server.GetCommand(updateSQL, con); + updateCmd.ExecuteNonQuery(); var tempFileToCreate = Path.Combine(TestContext.CurrentContext.TestDirectory, "allowList.yaml"); var allowList = File.Create(tempFileToCreate); allowList.Close(); @@ -65,14 +60,9 @@ public void RedactCHIsFromCatalogue_Allowlist() var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, tempFileToCreate); Assert.DoesNotThrow(() => cmd.Execute()); using var dt = new DataTable(); - using (var con = tbl.Database.Server.GetConnection()) - { - using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) - { - using var da = tbl.Database.Server.GetDataAdapter(findCmd); - da.Fill(dt); - } - } + using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) + using var da = tbl.Database.Server.GetDataAdapter(findCmd); + da.Fill(dt); Assert.That(dt.Rows.Count, Is.EqualTo(0)); } } From 701e4849ddaa122ea398cde88a0e0a04429fb650 Mon Sep 17 00:00:00 2001 From: James A Sutherland <> Date: Wed, 3 Jan 2024 12:37:31 -0600 Subject: [PATCH 63/64] Variable scope fix 2 --- .../ExecuteCommandRedactCHIsFromCatalogueTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs index 90f70ef7f5..1652d2e3cd 100644 --- a/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs +++ b/Rdmp.Core.Tests/CommandExecution/ExecuteCommandRedactCHIsFromCatalogueTests.cs @@ -60,7 +60,7 @@ public void RedactCHIsFromCatalogue_Allowlist() var cmd = new ExecuteCommandRedactCHIsFromCatalogue(new ThrowImmediatelyActivator(RepositoryLocator), biochemistry, tempFileToCreate); Assert.DoesNotThrow(() => cmd.Execute()); using var dt = new DataTable(); - using (var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con)) + using var findCmd = tbl.Database.Server.GetCommand($"select * from {tbl.GetRuntimeName()} where SampleType = 'F#########1'", con); using var da = tbl.Database.Server.GetDataAdapter(findCmd); da.Fill(dt); Assert.That(dt.Rows.Count, Is.EqualTo(0)); From ebc9e41f3f54c4d882377130ec6cc14766cd699c Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 30 Apr 2024 09:00:43 +0100 Subject: [PATCH 64/64] update ui --- CHANGELOG.md | 11 +++++++++-- .../Runtime/ExecuteCHIRedactionStage.cs | 2 +- .../up/078_add_chi_redaction.sql | 2 +- .../ViewRedactedCHIsInCatalogueDialog.cs | 15 +++++++-------- SharedAssemblyInfo.cs | 6 +++--- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 195c703871..d506103d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,23 @@ + # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [8.1.6] = Unreleased +## [8.2.0] - Unreleased + +## Changed + +- Add ability to redact CHI values on daata load and to redact existing catalogues + +## [8.1.6] - Unreleased ## Changed - Add Microsoft.Bcl.AsyncInterfaces 6.0.0 for plugin dependancy tree -- Add prompt to reanem container when adding a cohort filter +- Add prompt to rename container when adding a cohort filter ## [8.1.5] - 2024-04-03 diff --git a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs index fd15aa90f7..323e53ab26 100644 --- a/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs +++ b/Rdmp.Core/DataLoad/Engine/LoadExecution/Components/Runtime/ExecuteCHIRedactionStage.cs @@ -112,7 +112,7 @@ in dt.Columns.Cast() } else { - _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information, $"Found the CHI {foundChi} during the dataload")); + _job.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, $"Found the CHI {foundChi} during the dataload")); } } } diff --git a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql index 1ebcac4e99..b81284b9ad 100644 --- a/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql +++ b/Rdmp.Core/Databases/CatalogueDatabase/up/078_add_chi_redaction.sql @@ -1,4 +1,4 @@ ---Version:8.1.1 +--Version:8.2.0 --Description:Adds support for redacting CHI values on data load if not exists (select 1 from sys.tables where name = 'RedactedCHI') diff --git a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs index 2f903cffa7..605b14ca23 100644 --- a/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs +++ b/Rdmp.UI/SimpleDialogs/ViewRedactedCHIsInCatalogueDialog.cs @@ -38,7 +38,7 @@ private void RevertButtonClick(int itemIndex) var result = _results.Rows[itemIndex]; var potentialCHI = result.ItemArray[0].ToString(); var column = result.ItemArray[2].ToString(); - var table = result.ItemArray[3].ToString(); + var table = result.ItemArray[1].ToString(); var pkColumn = result.ItemArray[4].ToString(); var pkValue = result.ItemArray[5].ToString(); var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.ColumnName == column && rc.PKColumnName == pkColumn && rc.PKValue == pkValue && rc.TableName == table).First(); @@ -57,7 +57,7 @@ private void ConfirmButtonClick(int itemIndex) var result = _results.Rows[itemIndex]; var potentialCHI = result.ItemArray[0].ToString(); var column = result.ItemArray[2].ToString(); - var table = result.ItemArray[3].ToString(); + var table = result.ItemArray[1].ToString(); var pkColumn = result.ItemArray[4].ToString(); var pkValue = result.ItemArray[5].ToString(); var redactedChi = _catalogue.CatalogueRepository.GetAllObjects().Where(rc => rc.PotentialCHI == potentialCHI && rc.ColumnName == column && rc.PKColumnName == pkColumn && rc.PKValue == pkValue && rc.TableName == table).First(); @@ -117,15 +117,14 @@ private void FindChis() var dt = new DataTable(); dt.Columns.Add(new DataColumn("Potental CHI", typeof(string))); - dt.Columns.Add(new DataColumn("Context", typeof(string))); - dt.Columns.Add(new DataColumn("Column", typeof(string))); dt.Columns.Add(new DataColumn("Table", typeof(string))); + dt.Columns.Add(new DataColumn("Column", typeof(string))); + dt.Columns.Add(new DataColumn("PK", typeof(string))); dt.Columns.Add(new DataColumn("_pkColumn", typeof(string))); dt.Columns.Add(new DataColumn("_pkValue", typeof(string))); foreach (var rc in redactedChis) { - var context = "TODO"; - dt.Rows.Add(new object[] { rc.PotentialCHI, context, rc.ColumnName, rc.TableName, rc.PKColumnName, rc.PKValue }); + dt.Rows.Add(new object[] { rc.PotentialCHI, rc.TableName, rc.ColumnName, $"{rc.PKColumnName}:{rc.PKValue}", rc.PKColumnName, rc.PKValue }); } dtResults.DataSource = dt; if (_firstTime) @@ -147,11 +146,11 @@ private void FindChis() dtResults.CellClick += HandleClick; if (dtResults.Columns["Revert"] == null) { - dtResults.Columns.Insert(3, revertColumn); + dtResults.Columns.Insert(4, revertColumn); } if (dtResults.Columns["Confirm"] == null) { - dtResults.Columns.Insert(4, confirmColumn); + dtResults.Columns.Insert(5, confirmColumn); } _isLoading = false; lblLoading.Visible = _isLoading; diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs index bd9a535fe3..aa2132ac5d 100644 --- a/SharedAssemblyInfo.cs +++ b/SharedAssemblyInfo.cs @@ -10,6 +10,6 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("8.1.5")] -[assembly: AssemblyFileVersion("8.1.5")] -[assembly: AssemblyInformationalVersion("8.1.5")] +[assembly: AssemblyVersion("8.2.0")] +[assembly: AssemblyFileVersion("8.2.0")] +[assembly: AssemblyInformationalVersion("8.2.0")]