Skip to content

feat(tables-metadata): Add codelist parameter to tables metadata endpoint PXWEB2-654 #375

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

Merged
merged 8 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
158 changes: 158 additions & 0 deletions PxWeb.UnitTests/Data/PaxiomFixUtilTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using Note = PCAxis.Paxiom.Note;

namespace PxWeb.UnitTests.Data
{
[TestClass]
public class PaxiomFixUtilTests
{
[TestMethod]
public void ExtractNotes_WhenNoNotes_ReturnsEmptyDictionary()
{
// Arrange
var variable = new Variable();

// Act
var result = PaxiomFixUtil.ExtractNotes(variable);
// Assert
Assert.AreEqual(0, result.Count);
}

[TestMethod]
public void ExtractNotes_WhenNotesExist_ReturnsNotes()
{
// Arrange
var variable = new Variable("VAR1", "VAR1", PlacementType.Heading, 1);
var value = new Value("v1");
PaxiomUtil.SetCode(value, "v1");
variable.Values.Add(value);
value = new Value("v2");
PaxiomUtil.SetCode(value, "v2");
variable.Values.Add(value);
value = new Value("v3");
PaxiomUtil.SetCode(value, "v3");
variable.Values.Add(value);
variable.Values[0].AddNote(new Note("My note", NoteType.Value, true));

// Act
var result = PaxiomFixUtil.ExtractNotes(variable);
// Assert
Assert.AreEqual(1, result.Count);
Assert.IsTrue(result.ContainsKey("v1"));
Assert.AreEqual(1, result["v1"].Count);
}

[TestMethod]
public void RestoreNotes_WhenNotesExist_ReturnsNotes()
{
// Arrange
var variable = new Variable("VAR1", "VAR1", PlacementType.Heading, 1);
var value = new Value("v1");
PaxiomUtil.SetCode(value, "v1");
variable.Values.Add(value);
value = new Value("v2");
PaxiomUtil.SetCode(value, "v2");
variable.Values.Add(value);
value = new Value("v3");
PaxiomUtil.SetCode(value, "v3");
variable.Values.Add(value);
var notes = new Dictionary<string, Notes>();
var list = new Notes();
list.Add(new Note("My note", NoteType.Value, true));
notes.Add("v1", list);

// Act
PaxiomFixUtil.RestoreNotes(variable, notes);

// Assert
Assert.AreEqual(1, variable.Values[0].Notes.Count);
}

[TestMethod]
public void RestoreNotes_WhenNotesNotApplicable_ReturnsNotes()
{
// Arrange
var variable = new Variable("VAR1", "VAR1", PlacementType.Heading, 1);
var value = new Value("v1");
PaxiomUtil.SetCode(value, "v1");
variable.Values.Add(value);
value = new Value("v2");
PaxiomUtil.SetCode(value, "v2");
variable.Values.Add(value);
value = new Value("v3");
PaxiomUtil.SetCode(value, "v3");
variable.Values.Add(value);
var notes = new Dictionary<string, Notes>();
var list = new Notes();
list.Add(new Note("My note", NoteType.Value, true));
notes.Add("v4", list);

// Act
PaxiomFixUtil.RestoreNotes(variable, notes);

// Assert
Assert.IsFalse(variable.Values[0].HasNotes());
}

[TestMethod]
public void CleanCellnotes_WhenNoCellNotes_NoException()
{
// Arrange
var variable = new Variable("VAR1", "VAR1", PlacementType.Heading, 1);
var value = new Value("v1");
PaxiomUtil.SetCode(value, "v1");
variable.Values.Add(value);
value = new Value("v2");
PaxiomUtil.SetCode(value, "v2");
variable.Values.Add(value);
value = new Value("v3");
PaxiomUtil.SetCode(value, "v3");
variable.Values.Add(value);

var meta = new PXMeta();

// Act
var result = PaxiomFixUtil.CleanCellnotes(meta, variable);

// Assert
Assert.AreEqual(0, result);
}

[TestMethod]
public void CleanCellnotes_WhenCellNotes_MatchingWillBeRemoved()
{
// Arrange
var variable = new Variable("VAR1", "VAR1", PlacementType.Heading, 1);
var value = new Value("v1");
PaxiomUtil.SetCode(value, "v1");
variable.Values.Add(value);
value = new Value("v2");
PaxiomUtil.SetCode(value, "v2");
variable.Values.Add(value);
value = new Value("v3");
PaxiomUtil.SetCode(value, "v3");
variable.Values.Add(value);

var meta = new PXMeta();

var cellNote = new CellNote();
cellNote.Conditions.Add(new VariableValuePair("VAR1", "v4"));
cellNote.Conditions.Add(new VariableValuePair("VAR2", "v3"));
meta.CellNotes.Add(cellNote);

cellNote = new CellNote();
cellNote.Conditions.Add(new VariableValuePair("VAR2", "v3"));
meta.CellNotes.Add(cellNote);

cellNote = new CellNote();
cellNote.Conditions.Add(new VariableValuePair("VAR1", "v3"));
meta.CellNotes.Add(cellNote);

// Act
var result = PaxiomFixUtil.CleanCellnotes(meta, variable);

// Assert
Assert.AreEqual(1, result);
Assert.AreEqual(2, meta.CellNotes.Count);
}
}
}
41 changes: 41 additions & 0 deletions PxWeb.UnitTests/Data/SelectionHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,47 @@ public void Convert_InvalidSelection_ReturnsValidSelection()
Assert.AreEqual(3, result.Length);
}

[TestMethod]
public void FixVariableRefsAndApplyCodelists_WhenNoExistingVaiableSpecified_ReturnFalseAndProblem()
{
// Arrange
var codelist = new Dictionary<string, string>();
codelist.Add("THIS_IS_A_INVALID_CODE", "m1");
var variablesSelection = SelectionUtil.CreateVariablesSelectionFromCodelists(codelist);
var configMock = GetConfigMock();
var handler = new SelectionHandler(configMock.Object);
var model = ModelStore.CreateModelA();
var builderMock = new Mock<IPXModelBuilder>();
builderMock.Setup(x => x.Model).Returns(model);
handler.ExpandAndVerfiySelections(variablesSelection, builderMock.Object, out var problem);
// Act
var result = handler.FixVariableRefsAndApplyCodelists(builderMock.Object, variablesSelection, out problem);
// Assert
Assert.IsFalse(result);
Assert.IsNotNull(problem);
}

[TestMethod]
public void FixVariableRefsAndApplyCodelists_WhenNoExistingCodeListSpecified_ReturnFalseAndProblem()
{
// Arrange
var codelist = new Dictionary<string, string>();
codelist.Add("measure", "THIS_VALUE_DOES_NOT_EXIST");
var variablesSelection = SelectionUtil.CreateVariablesSelectionFromCodelists(codelist);
var configMock = GetConfigMock();
var handler = new SelectionHandler(configMock.Object);
var model = ModelStore.CreateModelA();
var builderMock = new Mock<IPXModelBuilder>();
builderMock.Setup(x => x.Model).Returns(model);
handler.ExpandAndVerfiySelections(variablesSelection, builderMock.Object, out var problem);
// Act
var result = handler.FixVariableRefsAndApplyCodelists(builderMock.Object, variablesSelection, out problem);
// Assert
Assert.IsFalse(result);
Assert.IsNotNull(problem);
}


private static VariablesSelection CreateValidSelection()
{
var selection = new VariablesSelection();
Expand Down
33 changes: 33 additions & 0 deletions PxWeb.UnitTests/Helpers/SelectionUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,38 @@ public void UseDefaultSelection_OneSelectionsDefined_ReturnsFalse()
Assert.IsFalse(useDefaultSelection);

}

[TestMethod]
public void CreateVariablesSelectionFromCodelists_WhenNoCodelist_ReturnsEmptySelection()
{
// Arrange
var codelist = new Dictionary<string, string>();
// Act
var result = SelectionUtil.CreateVariablesSelectionFromCodelists(codelist);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Selection.Count);
Assert.IsNotNull(result.Placement);
Assert.AreEqual(0, result.Placement.Heading.Count);
Assert.AreEqual(0, result.Placement.Stub.Count);
}

[TestMethod]
public void VariablesSelectionFromCodelists_WhenOneCodelist_ReturnsSelectionWithOneValue()
{
// Arrange
var codelist = new Dictionary<string, string>
{
{ "A", "B" }
};
// Act
var result = SelectionUtil.CreateVariablesSelectionFromCodelists(codelist);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Selection.Count);
Assert.AreEqual("A", result.Selection[0].VariableCode);
Assert.AreEqual("B", result.Selection[0].CodeList);

}
}
}
1 change: 1 addition & 0 deletions PxWeb/Code/Api2/DataSelection/ISelectionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public interface ISelectionHandler
{
bool ExpandAndVerfiySelections(VariablesSelection variablesSelection, IPXModelBuilder builder, out Problem? problem);
Selection[] Convert(VariablesSelection variablesSelection);
bool FixVariableRefsAndApplyCodelists(IPXModelBuilder builder, VariablesSelection variablesSelection, out Problem? problem);
}
}
53 changes: 53 additions & 0 deletions PxWeb/Code/Api2/DataSelection/PaxiomFixUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Linq;

using PCAxis.Paxiom;

namespace PxWeb.Code.Api2.DataSelection
{
public static class PaxiomFixUtil
{
public static int CleanCellnotes(PXMeta meta, Variable pxVariable)
{
int removed = 0;
for (int i = meta.CellNotes.Count - 1; i >= 0; i--)
{
var cellNote = meta.CellNotes[i];
// Check if there is a condition for the variable with a value that is no longer in the list of values
foreach (var condition in cellNote.Conditions.Where(c => string.Equals(c.VariableCode, pxVariable.Code, StringComparison.OrdinalIgnoreCase) &&
pxVariable.Values.FirstOrDefault(x => x.Code.Equals(c.ValueCode, StringComparison.InvariantCultureIgnoreCase)) is null))
{
meta.CellNotes.RemoveAt(i);
removed++;
}
}
return removed;
}

public static void RestoreNotes(Variable variable, Dictionary<string, Notes> notes)
{
foreach (var valueCode in notes.Keys)
{
if (variable.Values.FirstOrDefault(x => x.Code.Equals(valueCode, System.StringComparison.InvariantCultureIgnoreCase)) is Value value)
{
foreach (var note in notes[valueCode])
{
value.AddNote(note);
}
}
}
}

public static Dictionary<string, Notes> ExtractNotes(Variable variable)
{

// Extract notes
var notes = new Dictionary<string, Notes>();
foreach (var value in variable.Values.Where(v => v.HasNotes()))
{
notes.Add(value.Code, value.Notes);
}

return notes;
}
}
}
10 changes: 9 additions & 1 deletion PxWeb/Code/Api2/DataSelection/SelectionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
/// <param name="variablesSelection">The VariablesSelection object to verify and apply codelists for</param>
/// <param name="problem">Null if everything is ok, otherwise it describes whats wrong</param>
/// <returns>True if everything was ok, else false</returns>
private static bool FixVariableRefsAndApplyCodelists(IPXModelBuilder builder, VariablesSelection variablesSelection, out Problem? problem)
public bool FixVariableRefsAndApplyCodelists(IPXModelBuilder builder, VariablesSelection variablesSelection, out Problem? problem)
{
problem = null;

Expand Down Expand Up @@ -262,6 +262,8 @@

if (!string.IsNullOrWhiteSpace(variable.CodeList))
{
var notes = PaxiomFixUtil.ExtractNotes(pxVariable);

if (variable.CodeList.StartsWith("agg_"))
{
if (!ApplyGrouping(builder, pxVariable, variable, out problem))
Expand All @@ -281,8 +283,14 @@
problem = ProblemUtility.NonExistentCodelist();
return false;
}

// Restore notes
PaxiomFixUtil.RestoreNotes(pxVariable, notes);
// Remove cellnotes
PaxiomFixUtil.CleanCellnotes(builder.Model.Meta, pxVariable);
}


return true;
}

Expand Down Expand Up @@ -360,7 +368,7 @@
/// <returns></returns>
private static VariablesSelection AddVariables(VariablesSelection variablesSelection, PXModel model)
{
foreach (var variable in model.Meta.Variables)

Check warning on line 371 in PxWeb/Code/Api2/DataSelection/SelectionHandler.cs

View workflow job for this annotation

GitHub Actions / build

Loop should be simplified by calling Select(variable => variable.Code)) (https://rules.sonarsource.com/csharp/RSPEC-3267)

Check warning on line 371 in PxWeb/Code/Api2/DataSelection/SelectionHandler.cs

View workflow job for this annotation

GitHub Actions / build

Loop should be simplified by calling Select(variable => variable.Code)) (https://rules.sonarsource.com/csharp/RSPEC-3267)

Check warning on line 371 in PxWeb/Code/Api2/DataSelection/SelectionHandler.cs

View workflow job for this annotation

GitHub Actions / build

Loop should be simplified by calling Select(variable => variable.Code)) (https://rules.sonarsource.com/csharp/RSPEC-3267)
{
if (!variablesSelection.Selection.Any(x => x.VariableCode.Equals(variable.Code, System.StringComparison.InvariantCultureIgnoreCase)))
{
Expand Down
10 changes: 9 additions & 1 deletion PxWeb/Controllers/Api2/TableApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public TableApiController(IDataSource dataSource, ILanguageHelper languageHelper
_savedQueryBackendProxy = savedQueryBackendProxy;
}

public override IActionResult GetMetadataById([FromRoute(Name = "id"), Required] string id, [FromQuery(Name = "lang")] string? lang, [FromQuery(Name = "defaultSelection")] bool? defaultSelection)
public override IActionResult GetMetadataById([FromRoute(Name = "id"), Required] string id, [FromQuery(Name = "lang")] string? lang, [FromQuery(Name = "defaultSelection")] bool? defaultSelection, [FromQuery(Name = "codelist")] Dictionary<string, string>? codelist)
{
lang = _languageHelper.HandleLanguage(lang);
IPXModelBuilder? builder = _dataSource.CreateBuilder(id, lang);
Expand All @@ -98,6 +98,14 @@ public override IActionResult GetMetadataById([FromRoute(Name = "id"), Required]
_defaultSelectionAlgorithm.GetDefaultSelection(builder);
}
}
else if (codelist is not null && codelist.Keys.Count > 0) //Check that we have codelist specified
{
var selections = SelectionUtil.CreateVariablesSelectionFromCodelists(codelist);
if (!_selectionHandler.FixVariableRefsAndApplyCodelists(builder, selections, out Problem? problem))
{
return BadRequest(problem);
}
}

var model = builder.Model;
Dataset ds = _datasetMapper.Map(model, id, lang);
Expand Down
15 changes: 15 additions & 0 deletions PxWeb/Helper/Api2/SelectionUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ public static VariablesSelection CreateEmptyVariablesSelection()
return selections;
}

public static VariablesSelection CreateVariablesSelectionFromCodelists(Dictionary<string, string> codelist)
{
var selections = CreateEmptyVariablesSelection();

foreach (var key in codelist.Keys)
{
var selection = new VariableSelection();
selection.VariableCode = key;
selection.CodeList = codelist[key];
selections.Selection.Add(selection);
}

return selections;
}

/// <summary>
/// Adds a value to a variable selection. Only adds the value if it is not already in the selection
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion PxWeb/PxWeb.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<PackageReference Include="PCAxis.Menu.ConfigDatamodelMenu" Version="1.0.8" />
<PackageReference Include="PCAxis.Serializers" Version="1.7.0" />
<PackageReference Include="PcAxis.Sql" Version="1.4.1" />
<PackageReference Include="PxWeb.Api2.Server" Version="2.0.0-beta.16" />
<PackageReference Include="PxWeb.Api2.Server" Version="2.0.0-beta.17" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="8.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="8.1.1" />
Expand Down