Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release: 8.4.0 #2006

Merged
merged 66 commits into from
Nov 29, 2024
Merged
Changes from 25 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
2a516bf
add changelog
JFriel Sep 25, 2024
53596b0
Bugfix/rdmp 253 filter ordering (#2007)
JFriel Sep 26, 2024
bfdc644
update insert
JFriel Oct 8, 2024
c2ce5d3
Merge branch 'develop' of https://github.com/HicServices/RDMP into re…
JFriel Oct 10, 2024
ca5a0b7
tidy up
JFriel Oct 10, 2024
e27fd8e
interim
JFriel Oct 11, 2024
ece04aa
style updates
JFriel Oct 11, 2024
d1bcf49
add some info
JFriel Oct 11, 2024
0506490
start of where clause
JFriel Oct 16, 2024
34457ba
improved
JFriel Oct 16, 2024
6475d5e
improved dates
JFriel Oct 16, 2024
5ce3057
improve bookends
JFriel Oct 17, 2024
6edf05b
Merge branch 'develop' of https://github.com/HicServices/RDMP into re…
JFriel Oct 22, 2024
80b03e7
Merge branch 'develop' of https://github.com/HicServices/RDMP into re…
JFriel Oct 24, 2024
7e86428
Merge branch 'release/8.4.0' of https://github.com/HicServices/RDMP i…
JFriel Oct 24, 2024
2bf9ceb
add changelog
JFriel Oct 24, 2024
d8201c5
update changelog
JFriel Oct 24, 2024
b0f3388
Merge branch 'develop' of https://github.com/HicServices/RDMP into sp…
JFriel Oct 28, 2024
cb41028
improve ui with frequency
JFriel Oct 28, 2024
9885dc9
tidy up for tests
JFriel Oct 28, 2024
656cb6c
add summary
JFriel Oct 28, 2024
1bd3e4e
rename chart
JFriel Oct 29, 2024
40dd02e
interim people
JFriel Oct 29, 2024
ffcf62b
add people label
JFriel Oct 29, 2024
80518c4
better where
JFriel Oct 29, 2024
08b2ca1
move items around
JFriel Oct 29, 2024
4fe2f66
fix up tests
JFriel Oct 29, 2024
fdd9e28
tidy up code
JFriel Oct 30, 2024
c8f89d0
tidy up
JFriel Oct 30, 2024
f005f08
made db independant
JFriel Oct 30, 2024
298739e
Merge branch 'release/8.4.0' of https://github.com/HicServices/RDMP i…
JFriel Oct 30, 2024
f40ea83
add changelog
JFriel Oct 30, 2024
23cd4c1
Merge branch 'develop' of https://github.com/HicServices/RDMP into re…
JFriel Oct 30, 2024
65792a7
add missing file
JFriel Oct 30, 2024
65c2f42
Merge pull request #2022 from HicServices/bugfix/RDMP-256-migrate-com…
rdteviotdale Nov 6, 2024
7f4223d
Bugfix/rdmp 259 delta load off by one issue (#2024)
JFriel Nov 6, 2024
dbdb2b1
check for multi-server query
JFriel Nov 8, 2024
61f6fa0
Merge branch 'develop' of https://github.com/HicServices/RDMP into bu…
JFriel Nov 11, 2024
1d793fe
Merge branch 'release/8.4.0' of https://github.com/HicServices/RDMP i…
JFriel Nov 11, 2024
c73f595
update changelog
JFriel Nov 11, 2024
9e461f7
update changelog
JFriel Nov 11, 2024
6687ab1
Merge branch 'develop' of https://github.com/HicServices/RDMP into re…
JFriel Nov 11, 2024
f146d9a
Merge pull request #2059 from HicServices/bugfix/RDMP-266-multi-db-joins
rdteviotdale Nov 11, 2024
0779ce3
Add override for RAW table date column in delta loads (#2052)
JFriel Nov 12, 2024
a81d859
update deps
JFriel Nov 19, 2024
7bd6a33
add safe ref
JFriel Nov 19, 2024
a408e8c
Merge branch 'develop' into release/8.4.0
jas88 Nov 23, 2024
145600c
Merge branch 'release/8.4.0' into spike/overview-generation
JFriel Nov 25, 2024
1ab119a
interim
JFriel Nov 25, 2024
87ff689
Merge pull request #2050 from HicServices/spike/overview-generation
rdteviotdale Nov 25, 2024
cdd92f5
working check
JFriel Nov 26, 2024
644df6e
tidy up
JFriel Nov 26, 2024
224667a
Merge branch 'release/8.4.0' of https://github.com/HicServices/RDMP i…
JFriel Nov 26, 2024
8609d65
add changelog
JFriel Nov 26, 2024
d8f5576
fix typos
JFriel Nov 26, 2024
ff96edf
update typo
JFriel Nov 26, 2024
a36338f
fix typos
JFriel Nov 26, 2024
53609ab
fix typos
JFriel Nov 26, 2024
9bdb337
Merge pull request #2075 from HicServices/task/RDMP-267-depricate-on-…
bpeacock001 Nov 26, 2024
fcaef71
Changes made to popup buttons (#2076)
JBaird00183 Nov 27, 2024
090097e
Task/rdmp 32 regex redaction (#2009)
JFriel Nov 27, 2024
a7f0471
codeql updates
JFriel Nov 27, 2024
be9abf1
update client xml
JFriel Nov 27, 2024
70cd5a5
Merge pull request #2077 from HicServices/task/minor-codeql-updates
bpeacock001 Nov 27, 2024
ee270e1
code ql updates
JFriel Nov 29, 2024
a21a256
Merge branch 'develop' into release/8.4.0
JFriel Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [8.4.0] - Unreleased

- Add Ordering to Filters
- Add overview page for Catalogues
- Add RAW Table Date Column Override for Delta Loads
- Fix Delta Load off by one issue
- Update Migration strategy to account for all Primary Keys when moving from staging -> live
275 changes: 275 additions & 0 deletions Rdmp.Core/Curation/Data/Overview/OverviewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
// Copyright (c) The University of Dundee 2024-2024
// 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 <https://www.gnu.org/licenses/>.

using NPOI.SS.Formula.Functions;
using Rdmp.Core.CommandExecution;
using Rdmp.Core.DataExport.Data;
using Rdmp.Core.DataLoad.Triggers;
using Rdmp.Core.DataViewing;
using Rdmp.Core.Logging;
using Rdmp.Core.QueryBuilding;
using Rdmp.Core.Repositories;
using Rdmp.Core.ReusableLibraryCode.DataAccess;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace Rdmp.Core.Curation.Data.Overview;

/// <summary>
/// Used to populate information about a catalogue for use in the overview UI
/// </summary>
public class OverviewModel
{

private ICatalogue _catalogue;
private IBasicActivateItems _activator;

private DataTable _dataLoads;

private int _numberOfPeople;
private int _numberOfRecords;

public OverviewModel(IBasicActivateItems activator, ICatalogue catalogue)
{
_activator = activator;
_catalogue = catalogue;
if (catalogue != null)
{
Regen("");
}
}

public void Regen(string whereClause)
{
DataTable dt = new();
bool hasExtractionIdentifier = true;
var column = _catalogue.CatalogueItems.Where(ci => ci.ExtractionInformation.IsExtractionIdentifier).FirstOrDefault();
if (column is null)
{
column = _catalogue.CatalogueItems.FirstOrDefault();
hasExtractionIdentifier = false;
}
if (column is null) return;
var discoveredColumn = column.ColumnInfo.Discover(DataAccessContext.InternalDataProcessing);
var server = discoveredColumn.Table.Database.Server;
using var con = server.GetConnection();
con.Open();
string populatedWhere = !string.IsNullOrWhiteSpace(whereClause) ? $"WHERE {whereClause}" : "";
var sql = $"SELECT {column.ColumnInfo.GetRuntimeName()} FROM {discoveredColumn.Table.GetRuntimeName()} {populatedWhere}";
using var cmd = server.GetCommand(sql, con);
cmd.CommandTimeout = 30000;
using var da = server.GetDataAdapter(cmd);
dt.BeginLoadData();
da.Fill(dt);
dt.EndLoadData();
con.Dispose();
_numberOfRecords = dt.Rows.Count;
_numberOfPeople = hasExtractionIdentifier ? dt.DefaultView.ToTable(true, column.ColumnInfo.GetRuntimeName()).Rows.Count : 0;
GetDataLoads();
}

public int GetNumberOfRecords()
{
return _numberOfRecords;
}

public int GetNumberOfPeople()
{
return _numberOfPeople;
}

public Tuple<DateTime, DateTime> GetStartEndDates(ColumnInfo dateColumn, string whereClause)
{
DataTable dt = new();

Check warning

Code scanning / CodeQL

Missing Dispose call on local IDisposable Warning

Disposable 'DataTable' is created but not disposed.

var discoveredColumn = _catalogue.CatalogueItems.First().ColumnInfo.Discover(DataAccessContext.InternalDataProcessing);
var server = discoveredColumn.Table.Database.Server;
var populatedWhereClause = !string.IsNullOrWhiteSpace(whereClause) ? $"WHERE {whereClause}" : "";
using var con = server.GetConnection();
con.Open();
if (server.DatabaseType == FAnsi.DatabaseType.MicrosoftSQLServer)
{
var sql = $@"
select min({dateColumn.GetRuntimeName()}) as min, max({dateColumn.GetRuntimeName()}) as max
from
(select {dateColumn.GetRuntimeName()},
count(1) over (partition by year({dateColumn.GetRuntimeName()})) as occurs
from {discoveredColumn.Table.GetRuntimeName()} {populatedWhereClause}) as t
where occurs >1
";

using var cmd = server.GetCommand(sql, con);
cmd.CommandTimeout = 30000;
using var da = server.GetDataAdapter(cmd);
dt.BeginLoadData();
da.Fill(dt);
dt.EndLoadData();
}
else
{
var repo = new MemoryCatalogueRepository();
var qb = new QueryBuilder(null, null);
qb.AddColumn(new ColumnInfoToIColumn(repo, dateColumn));
qb.AddCustomLine($"{dateColumn.Name} IS NOT NULL", FAnsi.Discovery.QuerySyntax.QueryComponent.WHERE);
var cmd = server.GetCommand(qb.SQL, con);
using var da = server.GetDataAdapter(cmd);
dt.BeginLoadData();
da.Fill(dt);
var latest = dt.AsEnumerable()
.Max(r => r.Field<DateTime>(dateColumn.Name));
var earliest = dt.AsEnumerable()
.Min(r => r.Field<DateTime>(dateColumn.Name));
dt = new();

Check warning

Code scanning / CodeQL

Missing Dispose call on local IDisposable Warning

Disposable 'DataTable' is created but not disposed.
dt.Rows.Add([earliest, latest]);
}
con.Dispose();
return new Tuple<DateTime, DateTime>(DateTime.Parse(dt.Rows[0].ItemArray[0].ToString()), DateTime.Parse(dt.Rows[0].ItemArray[1].ToString()));
}


public static DataTable GetCountsByDatePeriod(ColumnInfo dateColumn, string datePeriod, string optionalWhere = "")
{
DataTable dt = new();
if (!(new[] { "Day", "Month", "Year" }).Contains(datePeriod))
{
throw new Exception("Invalid Date period");
}
var discoveredColumn = dateColumn.Discover(DataAccessContext.InternalDataProcessing);
var server = discoveredColumn.Table.Database.Server;
using var con = server.GetConnection();
con.Open();
var dateString = "yyyy-MM";
switch (datePeriod)
{
case "Day":
dateString = "yyyy-MM-dd";
break;
case "Month":
dateString = "yyyy-MM";
break;
case "Year":
dateString = "yyyy";
break;
}
if (server.DatabaseType == FAnsi.DatabaseType.MicrosoftSQLServer)
{
var sql = @$"
SELECT format({dateColumn.GetRuntimeName()}, '{dateString}') as YearMonth, count(*) as '# Records'
FROM {discoveredColumn.Table.GetRuntimeName()}
WHERE {dateColumn.GetRuntimeName()} IS NOT NULL
{(optionalWhere != "" ? "AND" : "")} {optionalWhere.Replace('"', '\'')}
GROUP BY format({dateColumn.GetRuntimeName()}, '{dateString}')
ORDER BY 1
";

using var cmd = server.GetCommand(sql, con);
cmd.CommandTimeout = 30000;
using var da = server.GetDataAdapter(cmd);
dt.BeginLoadData();
da.Fill(dt);
dt.EndLoadData();
}
else
{
var repo = new MemoryCatalogueRepository();
var qb = new QueryBuilder(null, null);
qb.AddColumn(new ColumnInfoToIColumn(repo, dateColumn));
qb.AddCustomLine($"{dateColumn.Name} IS NOT NULL", FAnsi.Discovery.QuerySyntax.QueryComponent.WHERE);
var cmd = server.GetCommand(qb.SQL, con);
using var da = server.GetDataAdapter(cmd);
dt.BeginLoadData();
da.Fill(dt);
Dictionary<string, int> counts = [];
foreach (var row in dt.AsEnumerable())
{
var datetime = DateTime.Parse(row.ItemArray[0].ToString());
var key = datetime.ToString(dateString);
counts[key]++;
}
dt = new DataTable();
foreach (var item in counts)
{
DataRow dr = dt.NewRow();
dr["YearMonth"] = item.Key;
dr["# Records"] = item.Value;
dt.Rows.Add(dr);
}
dt.EndLoadData();

}
con.Dispose();
return dt;
}

private void GetDataLoads()
{
_dataLoads = new();
var repo = new MemoryCatalogueRepository();
var qb = new QueryBuilder(null, null);
var columnInfo = _catalogue.CatalogueItems.Where(c => c.Name == SpecialFieldNames.DataLoadRunID).Select(c => c.ColumnInfo).FirstOrDefault();
if (columnInfo != null)
{
qb.AddColumn(new ColumnInfoToIColumn(repo, columnInfo));
qb.AddCustomLine($"{columnInfo.Name} IS NOT NULL", FAnsi.Discovery.QuerySyntax.QueryComponent.WHERE);
var sql = qb.SQL;
var server = columnInfo.Discover(DataAccessContext.InternalDataProcessing).Table.Database.Server;
using var con = server.GetConnection();
con.Open();

using var cmd = server.GetCommand(sql, con);
cmd.CommandTimeout = 30000;
using var da = server.GetDataAdapter(cmd);
_dataLoads.BeginLoadData();
da.Fill(_dataLoads);
_dataLoads.EndLoadData();
}

}

public DataTable GetMostRecentDataLoad()
{
if (_dataLoads == null) GetDataLoads();
if (_dataLoads.Rows.Count == 0) return null;
var maxDataLoadId = _dataLoads.AsEnumerable().Select(r => int.Parse(r[0].ToString())).Distinct().Max();
var loggingServers = _activator.RepositoryLocator.CatalogueRepository.GetAllObjectsWhere<ExternalDatabaseServer>("CreatedByAssembly", "Rdmp.Core/Databases.LoggingDatabase");
var columnInfo = _catalogue.CatalogueItems.Where(c => c.Name == SpecialFieldNames.DataLoadRunID).Select(c => c.ColumnInfo).First();
var server = columnInfo.Discover(DataAccessContext.InternalDataProcessing).Table.Database.Server;

DataTable dt = new();
foreach (var loggingServer in loggingServers)
{
var logCollection = new ViewLogsCollection(loggingServer, new LogViewerFilter(LoggingTables.DataLoadRun));
var dataLoadRunSql = $"{logCollection.GetSql()} WHERE ID={maxDataLoadId}";
var logServer = loggingServer.Discover(DataAccessContext.InternalDataProcessing).Server;
using var loggingCon = logServer.GetConnection();
loggingCon.Open();
using var loggingCmd = logServer.GetCommand(dataLoadRunSql, loggingCon);
loggingCmd.CommandTimeout = 30000;
using var loggingDa = server.GetDataAdapter(loggingCmd);
dt.BeginLoadData();
loggingDa.Fill(dt);
dt.EndLoadData();
loggingCon.Dispose();
if (dt.Rows.Count > 0)
{
break;
}
}
return dt;
}

public List<CumulativeExtractionResults> GetExtractions()
{
var datasets = _activator.RepositoryLocator.DataExportRepository.GetAllObjectsWhere<ExtractableDataSet>("Catalogue_ID", _catalogue.ID).Select(d => d.ID);
var results = _activator.RepositoryLocator.DataExportRepository.GetAllObjects<CumulativeExtractionResults>().Where(result => datasets.Contains(result.ExtractableDataSet_ID)).ToList();
return results;

}

}
Loading

Unchanged files with check annotations Beta

Name = r["Name"] as string;
IsMandatory = (bool)r["IsMandatory"];
ClonedFromExtractionFilter_ID = ObjectToNullableInt(r["ClonedFromExtractionFilter_ID"]);
Order = int.Parse(r["Order"].ToString());

Check warning

Code scanning / CodeQL

Virtual call in constructor or destructor Warning

Avoid virtual calls in a constructor or destructor.
var associatedColumnInfo_ID = r["AssociatedColumnInfo_ID"];
if (associatedColumnInfo_ID != DBNull.Value)
Description = r["Description"] as string;
Name = r["Name"] as string;
IsMandatory = (bool)r["IsMandatory"];
Order = int.Parse(r["Order"].ToString());

Check warning

Code scanning / CodeQL

Virtual call in constructor or destructor Warning

Avoid virtual calls in a constructor or destructor.
ClearAllInjections();
FilterContainer_ID = null;
ClonedFromExtractionFilter_ID = ObjectToNullableInt(r["ClonedFromExtractionFilter_ID"]);
Order = int.Parse(r["Order"].ToString());

Check warning

Code scanning / CodeQL

Virtual call in constructor or destructor Warning

Avoid virtual calls in a constructor or destructor.
}
/// <summary>
public class ExecuteCommandReorderFilter : BasicUICommandExecution
{
private ConcreteFilter _source;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note

Field '_source' can be 'readonly'.
private ConcreteFilter _target;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note

Field '_target' can be 'readonly'.
private InsertOption _insertOption;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note

Field '_insertOption' can be 'readonly'.
public ExecuteCommandReorderFilter(IActivateItems activator, ConcreteFilter source, ConcreteFilter destination, InsertOption insertOption) : base(activator)
{