Skip to content

Commit

Permalink
Merge pull request #28 from NatVanG/AddRuleIDProperty
Browse files Browse the repository at this point in the history
Add rule id property
NatVanG authored Jan 23, 2024
2 parents 426bb7f + fa28eb5 commit baead10
Showing 25 changed files with 1,395 additions and 83 deletions.
17 changes: 15 additions & 2 deletions DocsExamples/Example-rules.json
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
"codepage": 1200,
"rules": [
{
"id": "CHARTS_WIDER_THAN_TALL",
"name": "Charts wider than tall",
"description": "Want to check that your charts are wider than tall?",
"disabled": false,
@@ -49,6 +50,7 @@
]
},
{
"id": "DISABLE_SLOW_DATASOURCE_SETTINGS",
"name": "Disable local slow datasource settings",
"description": "Check that report slow data source settings are all disabled.",
"disabled": false,
@@ -91,6 +93,7 @@
]
},
{
"id": "LOCAL_REPORT_SETTINGS",
"name": "Local report settings",
"disabled": false,
"logType": "warning",
@@ -213,7 +216,8 @@
]
},
{
"name": "Show visual axes title",
"id": "SHOW_AXES_TITLES",
"name": "Show visual axes titles",
"description": "Check that certain charts have both axes title showing.",
"disabled": false,
"logType": "warning",
@@ -282,6 +286,7 @@
]
},
{
"id": "PERCENTAGE_OF_CHARTS_USING_CUSTOM_COLOURS",
"name": "Percentage of charts across the report using custom colours is not greater than 10%",
"description": "Check that charts avoid custom colours and use theme colours instead.",
"disabled": false,
@@ -372,6 +377,7 @@
]
},
{
"id": "ENSURE_ALT_TEXT_DEFINED_FOR_VISUALS",
"name": "Ensure alt-text has been defined for visuals",
"description": "Alt-text is required for screen readers.",
"disabled": true,
@@ -441,6 +447,7 @@
]
},
{
"id": "DISABLE_DROP_SHADOWS_ON_VISUALS",
"name": "Disable drop shadows on visuals",
"description": "Drop shadows are not suitable for everyone, this rule returns an array of visuals with drop shadows enabled.",
"disabled": false,
@@ -485,6 +492,7 @@
]
},
{
"id": "GIVE_VISIBLE_PAGES_MEANINGFUL_NAMES",
"name": "Give visible pages meaningful names",
"description": "Returns an array of visible page names with a default 'Page x' display name.",
"disabled": false,
@@ -533,6 +541,7 @@
]
},
{
"id": "DENEB_CHARTS_PROPERTIES",
"name": "Check Deneb charts properties - work in progress",
"description": "Checks that the drillvar custom rule can read deneb custom visual nested jsonspec properties. This is an example in progress that demonstrates the use of the drillvar custom rule but doesn't yet do anything useful.",
"disabled": true,
@@ -581,6 +590,7 @@
]
},
{
"id": "CHECK_FOR_VISUALS_OVERLAP",
"name": "Check for visuals overlap with a 5px margin",
"description": "Returns names of visuals that overlap while inflating visuals rectangle area by 5px left, right, top and bottom. Currently this does not check for overlap with the sides of report page itself. This rule does not currently work with visual groups.",
"disabled": false,
@@ -691,10 +701,11 @@
]
},
{
"id": "CHECK_FOR_LOCAL_MEASURES",
"name": "Check for locally defined measures",
"description": "Returns an array of report-level measure definitions",
"path": "$.config",
"pathErrorWhenNoMatch": true,
"pathErrorWhenNoMatch": false,
"test": [
{
"filter": [
@@ -751,6 +762,7 @@
"codepage": 65001,
"rules": [
{
"id": "REPORT_THEME_NAME",
"name": "Report theme name",
"description": "Check Report theme name",
"disabled": false,
@@ -771,6 +783,7 @@
]
},
{
"id": "REPORT_THEME_TITLE_FONT",
"name": "Report theme title font properties",
"description": "Checks theme's' title foreground, fontface and fontsize",
"disabled": false,
1,038 changes: 1,038 additions & 0 deletions DocsExamples/Themeable-properties-rules.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions PBIXInspector.sln
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DocsExamples", "DocsExample
DocsExamples\Example-ReportPageFieldMap.json = DocsExamples\Example-ReportPageFieldMap.json
DocsExamples\Example-rules.json = DocsExamples\Example-rules.json
DocsExamples\Reid-rules.json = DocsExamples\Reid-rules.json
DocsExamples\Themeable-properties-rules.json = DocsExamples\Themeable-properties-rules.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rules", "Rules", "{BB86A63C-BBD5-48DA-9481-343165781823}"
6 changes: 3 additions & 3 deletions PBIXInspectorCLI/PBIXInspectorCLI.csproj
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@
<StartupObject>Program</StartupObject>
<SignAssembly>False</SignAssembly>
<IsPublishable>True</IsPublishable>
<FileVersion>1.9.4.0</FileVersion>
<AssemblyVersion>1.9.4</AssemblyVersion>
<VersionPrefix>1.9.4</VersionPrefix>
<FileVersion>1.9.5.0</FileVersion>
<AssemblyVersion>1.9.5</AssemblyVersion>
<VersionPrefix>1.9.5</VersionPrefix>
<Version>$(VersionPrefix)</Version>
<Product>$(AssembblyName)</Product>
<ApplicationIcon>pbiinspector.ico</ApplicationIcon>
2 changes: 2 additions & 0 deletions PBIXInspectorLibrary/InspectionRules.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
/// </summary>
public class InspectionRules : IInspectionRules
{
public List<PbiEntry> PbiEntries { get; set; }

Check warning on line 8 in PBIXInspectorLibrary/InspectionRules.cs

GitHub Actions / build (Release)

Non-nullable property 'PbiEntries' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}

public class PbiEntry
@@ -34,13 +34,15 @@

public class Rule
{
public string Id { get; set; }

Check warning on line 37 in PBIXInspectorLibrary/InspectionRules.cs

GitHub Actions / build (Release)

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public string Name { get; set; }

Check warning on line 39 in PBIXInspectorLibrary/InspectionRules.cs

GitHub Actions / build (Release)

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public string Description { get; set; }

Check warning on line 41 in PBIXInspectorLibrary/InspectionRules.cs

GitHub Actions / build (Release)

Non-nullable property 'Description' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public bool Disabled { get; set; }

public string LogType { get; set; }

Check warning on line 45 in PBIXInspectorLibrary/InspectionRules.cs

GitHub Actions / build (Release)

Non-nullable property 'LogType' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public string ForEachPath { get; set; }

27 changes: 6 additions & 21 deletions PBIXInspectorLibrary/Inspector.cs
Original file line number Diff line number Diff line change
@@ -73,22 +73,7 @@ public Inspector(string pbiFilePath, string rulesFilePath) : base(pbiFilePath, r
AddCustomRulesToRegistry();
}

private PbiFile InitPbiFile(string pbiFilePath)
{
switch (PbiFile.PBIFileType(pbiFilePath))
{
case PbiFile.PBIFileTypeEnum.PBIX:
return new PbixFile(pbiFilePath);
break;
case PbiFile.PBIFileTypeEnum.PBIP:
return new PbipFile(pbiFilePath);
break;
case PbiFile.PBIFileTypeEnum.PBIPReport:
return new PbipReportFile(pbiFilePath);
default:
throw new PBIXInspectorException(string.Format("Could not determine the extension of PBI file with path \"{0}\".", pbiFilePath));
}
}


private void AddCustomRulesToRegistry()
{
@@ -124,7 +109,7 @@ public IEnumerable<TestResult> Inspect()
{
var testResults = new List<TestResult>();

using (var pbiFile = InitPbiFile(_pbiFilePath))
using (var pbiFile = PbiFileUtils.InitPbiFile(_pbiFilePath))
{
foreach (var entry in this._inspectionRules.PbiEntries)
{
@@ -228,11 +213,11 @@ public IEnumerable<TestResult> Inspect()
string resultString = string.Concat("\"", strForEachDisplayName, "\" - ", string.Format("Rule \"{0}\" {1} with result: {2}, expected: {3}.", rule != null ? rule.Name : string.Empty, result ? "PASSED" : "FAILED", jruleresult != null ? jruleresult.ToString() : string.Empty, rule.Test.Expected != null ? rule.Test.Expected.ToString() : string.Empty));

//yield return new TestResult { RuleName = rule.Name, ParentName = strForEachName, ParentDisplayName = strForEachDisplayName, Pass = result, Message = resultString, Expected = rule.Test.Expected, Actual = jruleresult};
testResults.Add(new TestResult { RuleName = rule.Name, LogType = ruleLogType, RuleDescription = rule.Description, ParentName = strForEachName, ParentDisplayName = strForEachDisplayName, Pass = result, Message = resultString, Expected = rule.Test.Expected, Actual = jruleresult });
testResults.Add(new TestResult { RuleId = rule.Id, RuleName = rule.Name, LogType = ruleLogType, RuleDescription = rule.Description, ParentName = strForEachName, ParentDisplayName = strForEachDisplayName, Pass = result, Message = resultString, Expected = rule.Test.Expected, Actual = jruleresult });
}
catch (PBIXInspectorException e)
{
testResults.Add(new TestResult { RuleName = rule.Name, LogType = MessageTypeEnum.Error, RuleDescription = rule.Description, ParentName = strForEachName, ParentDisplayName = strForEachDisplayName, Pass = false, Message = e.Message, Expected = rule.Test.Expected, Actual = null });
testResults.Add(new TestResult { RuleId = rule.Id, RuleName = rule.Name, LogType = MessageTypeEnum.Error, RuleDescription = rule.Description, ParentName = strForEachName, ParentDisplayName = strForEachDisplayName, Pass = false, Message = e.Message, Expected = rule.Test.Expected, Actual = null });
continue;
}
}
@@ -260,11 +245,11 @@ public IEnumerable<TestResult> Inspect()
string resultString = string.Format("Rule \"{0}\" {1} with result: {2}, expected: {3}.", rule != null ? rule.Name : string.Empty, result ? "PASSED" : "FAILED", jruleresult != null ? jruleresult.ToString() : string.Empty, rule.Test.Expected != null ? rule.Test.Expected.ToString() : string.Empty);

//yield return new TestResult { RuleName = rule.Name, Pass = result, Message = resultString, Expected = rule.Test.Expected, Actual = jruleresult };
testResults.Add(new TestResult { RuleName = rule.Name, LogType = ruleLogType, RuleDescription = rule.Description, Pass = result, Message = resultString, Expected = rule.Test.Expected, Actual = jruleresult });
testResults.Add(new TestResult { RuleId = rule.Id, RuleName = rule.Name, LogType = ruleLogType, RuleDescription = rule.Description, Pass = result, Message = resultString, Expected = rule.Test.Expected, Actual = jruleresult });
}
catch (PBIXInspectorException e)
{
testResults.Add(new TestResult { RuleName = rule.Name, LogType = MessageTypeEnum.Error, RuleDescription = rule.Description, Pass = false, Message = e.Message, Expected = rule.Test.Expected, Actual = null });
testResults.Add(new TestResult { RuleId = rule.Id, RuleName = rule.Name, LogType = MessageTypeEnum.Error, RuleDescription = rule.Description, Pass = false, Message = e.Message, Expected = rule.Test.Expected, Actual = null });
continue;
}
}
2 changes: 2 additions & 0 deletions PBIXInspectorLibrary/Output/TestResult.cs
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ public class TestResult
{
public Guid Id { get; private set; }

public string? RuleId { get; set; }

public string RuleName { get; set; }

public string? RuleDescription { get; set; }
26 changes: 26 additions & 0 deletions PBIXInspectorLibrary/PbiFileUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleToAttribute("PBIXInspectorTests")]

namespace PBIXInspectorLibrary
{
internal class PbiFileUtils
{
internal static PbiFile InitPbiFile(string pbiFilePath)
{
if (string.IsNullOrEmpty(pbiFilePath)) throw new PBIXInspectorException("PBI file path is empty.");

switch (PbiFile.PBIFileType(pbiFilePath))
{
case PbiFile.PBIFileTypeEnum.PBIX:
return new PbixFile(pbiFilePath);
case PbiFile.PBIFileTypeEnum.PBIP:
return new PbipFile(pbiFilePath);
case PbiFile.PBIFileTypeEnum.PBIPReport:
return new PbipReportFile(pbiFilePath);
default:
throw new PBIXInspectorException(string.Format("Could not determine the extension of PBI file with path \"{0}\".", pbiFilePath));
}
}
}
}
18 changes: 18 additions & 0 deletions PBIXInspectorTests/CLIArgsUtilsTest.cs
Original file line number Diff line number Diff line change
@@ -217,6 +217,24 @@ public void TestCLIArgsUtilsResolvePbiFilePathInput3()
Assert.IsTrue(resolvedPath == expectedPath);
}

[Test]
public void TestCLIArgsUtilsResolvePbipFilePathThrows()
{
string inputPath = @"C:\TEMP\VisOps\Sales - custom colours.pbip";

ArgumentException ex = Assert.Throws<ArgumentException>(
() => ArgsUtils.ResolvePbiFilePathInput(inputPath));
}

[Test]
public void TestCLIArgsUtilsResolvePbirFilePathThrows()
{
string inputPath = @"C:\TEMP\VisOps\Sales - custom colours.Report\definition.pbir";

ArgumentException ex = Assert.Throws<ArgumentException>(
() => ArgsUtils.ResolvePbiFilePathInput(inputPath));
}

[Test]
public void TestCLIArgsUtilsSuccess_FormatsOption()
{
Binary file added PBIXInspectorTests/Files/Example-rules-fails.pbix
Binary file not shown.
10 changes: 8 additions & 2 deletions PBIXInspectorTests/PBIXInspectorTests.csproj
Original file line number Diff line number Diff line change
@@ -30,6 +30,9 @@
<None Include="..\PBIXInspectorWinForm\Files\pbip\Inventory sample - fails.Report\report.json" Link="Files\pbip\Inventory sample - fails.Report\report.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="..\DocsExamples\Example-rules.json" Link="Files\Example-rules.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\PBIXInspectorWinForm\Files\pbip\Inventory sample - fails.Report\StaticResources\RegisteredResources\pbilogo011716949638171492.png" Link="Files\pbip\Inventory sample - fails.Report\StaticResources\RegisteredResources\pbilogo011716949638171492.png" />
<None Include="..\PBIXInspectorWinForm\Files\pbip\Inventory sample - fails.Report\StaticResources\SharedResources\BaseThemes\CY22SU11.json" Link="Files\pbip\Inventory sample - fails.Report\StaticResources\SharedResources\BaseThemes\CY22SU11.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -62,16 +65,19 @@
</ItemGroup>

<ItemGroup>
<None Update="Files\Example-rules-fails.pbix">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\Inventory rules sample.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\Inventory rules test.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\Inventory sample - fails.pbix">
<None Update="Files\Base-rules-fails.pbix">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\Inventory sample - passes.pbix">
<None Update="Files\Base-rules-passes.pbix">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\Inventory sample.pbix">
36 changes: 36 additions & 0 deletions PBIXInspectorTests/PbiFileUtilsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma warning disable CS8602

using PBIXInspectorLibrary;

namespace PBIXInspectorTests
{
public class PbiFileUtilsTests
{
[Test]
public void PbiFileUtilsThrows()
{
PbiFile pbiFile = null;
string pbiFilePath = null;
PBIXInspectorException ex = Assert.Throws<PBIXInspectorException>(
() => pbiFile = PbiFileUtils.InitPbiFile(pbiFilePath));
}

[Test]
public void PbiFileUtilsThrows2()
{
PbiFile pbiFile = null;
string pbiFilePath = string.Empty;
PBIXInspectorException ex = Assert.Throws<PBIXInspectorException>(
() => pbiFile = PbiFileUtils.InitPbiFile(pbiFilePath));
}

[Test]
public void PbiFileUtilsThrows3()
{
PbiFile pbiFile = null;
string pbiFilePath = "myreport.xxxx";
PBIXInspectorException ex = Assert.Throws<PBIXInspectorException>(
() => pbiFile = PbiFileUtils.InitPbiFile(pbiFilePath));
}
}
}
150 changes: 136 additions & 14 deletions PBIXInspectorTests/SuiteRunner.cs
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ public void RunPbipTest(TestResult testResult)
#region BasePassSuite
public static IEnumerable<TestCaseData> BasePassPBIXSuite()
{
string PBIXFilePath = @"Files\Inventory sample - passes.pbix";
string PBIXFilePath = @"Files\Base-rules-passes.pbix";
string RulesFilePath = @"Files\Base-rules.json";

Console.WriteLine("Running base pass PBIX suite...");
@@ -86,7 +86,7 @@ public void RunBasePassPBIX(TestResult testResult)
#region BaseFailSuite
public static IEnumerable<TestCaseData> BaseFailPBIXSuite()
{
string PBIXFilePath = @"Files\Inventory sample - fails.pbix";
string PBIXFilePath = @"Files\Base-rules-fails.pbix";
string RulesFilePath = @"Files\Base-rules.json";

Console.WriteLine("Running base fail PBIX suite...");
@@ -117,13 +117,13 @@ public void RunBaseFailPBIP(TestResult testResult)
private void RunBaseFail(TestResult testResult)
{
string expected = "[]";
switch (testResult.RuleName)
switch (testResult.RuleId)
{
case "Remove custom visuals which are not used in the report.":
case "REMOVE_UNUSED_CUSTOM_VISUALS":
expected = "[\"Aquarium1442671919391\"]";
JsonAssert.AreEquivalent(testResult.Actual, JsonNode.Parse(expected));
break;
case "Reduce the number of visible visuals on the page":
case "REDUCE_VISUALS_ON_PAGE":
if (testResult.ParentName == "ReportSectionfb0835fa991786b43a3f")
{
Assert.False(testResult.Pass, testResult.Message);
@@ -133,7 +133,7 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Reduce the number of objects within visuals":
case "REDUCE_OBJECTS_WITHIN_VISUALS":
if (testResult.ParentName == "ReportSection4602098ba1ff5a3805a9")
{
Assert.False(testResult.Pass, testResult.Message);
@@ -143,7 +143,7 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Reduce usage of TopN filtering visuals by page":
case "REDUCE_TOPN_FILTERS":
if (testResult.ParentName == "ReportSection3440cc1dc4ec63ca3d06")
{
Assert.False(testResult.Pass, testResult.Message);
@@ -153,7 +153,7 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Reduce usage of Advanced filtering visuals by page":
case "REDUCE_ADVANCED_FILTERS":
if (testResult.ParentName == "ReportSectiond7d52b137add50d28b88")
{
Assert.False(testResult.Pass, testResult.Message);
@@ -163,10 +163,10 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Reduce number of pages per report":
case "REDUCE_PAGES":
Assert.True(testResult.Pass, testResult.Message);
break;
case "Avoid setting ‘Show items with no data’ on columns":
case "AVOID_SHOW_ITEMS_WITH_NO_DATA":
expected = "[\"797168e1f1e7658ceae6\",\"97ad01a2b8fbfca3220c\"]";
if (testResult.ParentName == "ReportSection5f326c8a8185db501ad9")
{
@@ -177,7 +177,7 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Tooltip and Drillthrough pages should be hidden":
case "HIDE_TOOLTIP_DRILLTROUGH_PAGES":
if (testResult.ParentName == "ReportSectionadc267c0d12e40458799"
|| testResult.ParentName == "ReportSection8952e5fd70dcea579d3b")
{
@@ -188,7 +188,7 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Ensure charts use theme colours":
case "ENSURE_THEME_COLOURS":
if (testResult.ParentName == "ReportSection6c3c3f97279fafdeeb57")
{
expected = "[\"1a67964cf02b6170c3b8\"]";
@@ -199,11 +199,133 @@ private void RunBaseFail(TestResult testResult)
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "Ensure pages do not scroll vertically":
case "ENSURE_PAGES_DO_NOT_SCROLL_VERTICALLY":
expected = "[\"Scrolling page\"]";
JsonAssert.AreEquivalent(testResult.Actual, JsonNode.Parse(expected));
break;
case "Ensure alternativeText has been defined for all visuals":
case "ENSURE_ALTTEXT":
Assert.False(testResult.Pass, testResult.Message);
break;
default:
Assert.True(testResult.Pass, testResult.Message);
break;
}
}
#endregion

#region ExampleFailSuite

public static IEnumerable<TestCaseData> ExampleFailPBIXSuite()
{
string PBIXFilePath = @"Files\Example-rules-fails.pbix";
string RulesFilePath = @"Files\Example-rules.json";

Console.WriteLine("Running example fail PBIX suite...");
return Suite(PBIXFilePath, RulesFilePath);
}

[TestCaseSource(nameof(ExampleFailPBIXSuite))]
public void RunExampleFailPBIX(TestResult testResult)
{
RunExampleFail(testResult);
}

private void RunExampleFail(TestResult testResult)
{
string expected = "[]";
switch (testResult.RuleId)
{
case "CHARTS_WIDER_THAN_TALL":
if (testResult.ParentDisplayName == testResult.RuleId)
{
expected = "[\"3f7d302598c1e81e7e78\", \"5094f3ff553da63e610e\"]";
JsonAssert.AreEquivalent(JsonNode.Parse(expected), testResult.Actual);
Assert.False(testResult.Pass, testResult.Message);
}
else
{
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "DISABLE_SLOW_DATASOURCE_SETTINGS":
Assert.False(testResult.Pass, testResult.Message);
break;
case "LOCAL_REPORT_SETTINGS":
Assert.False(testResult.Pass, testResult.Message);
break;
case "SHOW_AXES_TITLES":
if (testResult.ParentDisplayName == testResult.RuleId)
{
expected = "[\"a9243890e8b7ec111322\", \"d65c53d5b679c4cacba0\", \"8a0d8392a2400e899bcc\"]";
JsonAssert.AreEquivalent(JsonNode.Parse(expected), testResult.Actual);
Assert.False(testResult.Pass, testResult.Message);
}
else
{
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "PERCENTAGE_OF_CHARTS_USING_CUSTOM_COLOURS":
//if (testResult.ParentName == "ReportSectiond7d52b137add50d28b88")
//{
// Assert.False(testResult.Pass, testResult.Message);
//}
//else
//{
// Assert.True(testResult.Pass, testResult.Message);
//}
break;
case "ENSURE_ALT_TEXT_DEFINED_FOR_VISUALS":
expected = "[\"9032ab70a7e060d08574\",\"eca6ff83ecb390801c3a\"]";
if (testResult.ParentDisplayName == testResult.RuleId)
{
JsonAssert.AreEquivalent(JsonNode.Parse(expected), testResult.Actual);
Assert.False(testResult.Pass, testResult.Message);
}

break;
case "DISABLE_DROP_SHADOWS_ON_VISUALS":
expected = "[\"bdb3c2666ac0e67947aa\",\"5d4868734a72096e0ada\"]";
if (testResult.ParentDisplayName == testResult.RuleId)
{
JsonAssert.AreEquivalent(JsonNode.Parse(expected), testResult.Actual);
Assert.False(testResult.Pass, testResult.Message);
}
else
{
Assert.True(testResult.Pass, testResult.Message);
}
break;
case "GIVE_VISIBLE_PAGES_MEANINGFUL_NAMES":
if (testResult.ParentDisplayName == "Page 1")
{
Assert.False(testResult.Pass, testResult.Message);
}
else
{
Assert.False(testResult.Pass, testResult.Message);
}
break;
case "DENEB_CHARTS_PROPERTIES":
//TODO: complete this test
//Assert.False(testResult.Pass, testResult.Message);
break;
case "CHECK_FOR_VISUALS_OVERLAP":
expected = "[\"2beb787442a6d0432b4d\",\"11f540db1a90abb52cda\",\"93e80741178005eb0ab4\",\"dead16c359819062e164\"]";
if (testResult.ParentDisplayName == testResult.RuleId)
{
JsonAssert.AreEquivalent(JsonNode.Parse(expected), testResult.Actual);
Assert.False(testResult.Pass, testResult.Message);
}
break;
case "CHECK_FOR_LOCAL_MEASURES":
//TODO: complete this test
//Assert.False(testResult.Pass, testResult.Message);
break;
case "REPORT_THEME_NAME":
Assert.False(testResult.Pass, testResult.Message);
break;
case "REPORT_THEME_TITLE_FONT":
Assert.False(testResult.Pass, testResult.Message);
break;
default:
17 changes: 9 additions & 8 deletions PBIXInspectorWinForm/MainForm.cs
Original file line number Diff line number Diff line change
@@ -5,8 +5,7 @@ namespace PBIXInspectorWinForm
{
public partial class MainForm : Form
{
private static Args _args;


public MainForm()
{
InitializeComponent();
@@ -112,17 +111,19 @@ private void chckUseTempFiles_CheckedChanged(object sender, EventArgs e)
private void btnRun_Click(object sender, EventArgs e)
{
Clear();

btnRun.Enabled = false;
var pbiFilePath = ArgsUtils.ResolvePbiFilePathInput(this.txtPBIDesktopFile.Text);

var pbiFilePath = this.txtPBIDesktopFile.Text;
var rulesFilePath = this.txtRulesFilePath.Text;
var outputPath = this.txtOutputDirPath.Text;
var verboseString = this.chckVerbose.Checked.ToString();
var formatsString = string.Concat(this.chckJsonOutput.Checked ? "JSON" : string.Empty, ",", this.chckHTMLOutput.Checked ? "HTML" : string.Empty);
_args = new Args { PBIFilePath = pbiFilePath, RulesFilePath = rulesFilePath, OutputPath = outputPath, FormatsString = formatsString, VerboseString = verboseString };
var verbose = this.chckVerbose.Checked;
var jsonOutput = this.chckJsonOutput.Checked;
var htmlOutput = this.chckHTMLOutput.Checked;

Main.Run(_args);
btnRun.Enabled = true;
Main.Run(pbiFilePath, rulesFilePath, outputPath, verbose, jsonOutput, htmlOutput);

btnRun.Enabled = true;
}

internal void Clear()
6 changes: 3 additions & 3 deletions PBIXInspectorWinForm/PBIXInspectorWinForm.csproj
Original file line number Diff line number Diff line change
@@ -6,9 +6,9 @@
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<FileVersion>1.9.4.0</FileVersion>
<AssemblyVersion>1.9.4</AssemblyVersion>
<VersionPrefix>1.9.4</VersionPrefix>
<FileVersion>1.9.5.0</FileVersion>
<AssemblyVersion>1.9.5</AssemblyVersion>
<VersionPrefix>1.9.5</VersionPrefix>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>pbiinspector.png</PackageIcon>
<PackageProjectUrl>https://github.com/NatVanG/PBIXInspector</PackageProjectUrl>
2 changes: 1 addition & 1 deletion PBIXInspectorWinForm/PBIXInspectorWinForm.pbitool.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.9.4",
"version": "1.9.5",
"name": "VisOps with PBI Inspector",
"description": "A testing or inspection tool for the visual layer of Microsoft Power BI reports.",
"path": "%PATH%",
2 changes: 2 additions & 0 deletions PBIXInspectorWinLibrary/Constants.cs
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@ public static class Constants
public const string SampleRulesFilePath = @"Files\Base-rules.json";
public const string ReportPageFieldMapFilePath = @"Files\ReportPageFieldMap.json";
public const string PBIPReportJsonFileName = "report.json";
public const string PBIPFileExtension = ".pbip";
public const string PBIRFileExtension = ".pbir";
public const string TestRunHTMLTemplate = @"Files\html\TestRunTemplate.html";
public const string PBIInspectorPNG = @"Files\icon\pbiinspector.png";
public const string TestRunHTMLFileName = "TestRun.html";
13 changes: 10 additions & 3 deletions PBIXInspectorWinLibrary/Drawing/ImageUtils.cs
Original file line number Diff line number Diff line change
@@ -45,11 +45,18 @@ public static void DrawReportPages(IEnumerable<TestResult> fieldMapResults, IEnu
var width = (int)Math.Round(f["width"].GetValue<decimal>());
var visible = f["visible"].GetValue<bool>();

var pass = !(testResult.Actual != null && testResult.Actual is JsonArray
//If a visual name is returned in the test actual array then highlight it as a test failure in the page wireframe
//A visual name can be returned either as a JsonValue or a named JsonObject (i.e. {"name": "VisualName"}) hence the "or else" operator below (i.e. "||")
var visualNameInTestActualArray = (testResult.Actual != null && testResult.Actual is JsonArray
&& testResult.Actual.AsArray().Any(_ => _ != null
&& _ is JsonValue && _.AsValue().ToString().Equals(name)));
&& _ is JsonValue && _.AsValue().ToString().Equals(name)))
|| (testResult.Actual != null && testResult.Actual is JsonArray
&& testResult.Actual.AsArray().Any(_ => _ != null && _ is JsonObject && _ is not JsonValue
&& _["name"] != null && _["name"] is JsonValue && _["name"].AsValue().ToString().Equals(name)));


visuals.Add(new ReportPage.VisualContainer { Name = name.ToString(), VisualType = visualType.ToString(), X = x, Y = y, Height = height, Width = width, Pass = pass, Visible = visible });
bool visualPass = !visualNameInTestActualArray;
visuals.Add(new ReportPage.VisualContainer { Name = name.ToString(), VisualType = visualType.ToString(), X = x, Y = y, Height = height, Width = width, Pass = visualPass, Visible = visible });
}

var rp = new ReportPage(pageName, pageDisplayName, pageSize, visuals);
13 changes: 12 additions & 1 deletion PBIXInspectorWinLibrary/Files/html/TestRunTemplate.html
Original file line number Diff line number Diff line change
@@ -113,6 +113,17 @@ <h6 class="border-bottom pb-2 mb-0">Results</h6>
"html": [{ "<>": "strong", "class": "d-block text-gray-dark", "text": "Verbose" }, { "text": "${Verbose}" }]
}
]
},
{
"<>": "div",
"class": "d-flex text-body-secondary pt-3",
"html": [
{
"<>": "p",
"class": "pb-3 mb-0 small lh-sm",
"html": [{ "<>": "strong", "class": "d-block text-gray-dark", "text": "Results stats" }, { "text": function () { if (this.Results != null) { return "Results count: " + this.Results.length } else { return "No results were found."} } }]
}
]
}
]
};
@@ -227,7 +238,7 @@ <h6 class="border-bottom pb-2 mb-0">Results</h6>
]
}
]
}, { "<>": "img", "src": function () { if (this.ParentName != null) { return "PBIInspectorPNG\\" + this.Id + ".png" } else { return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAAAxJREFUGFdj+P//PwAF/gL+pzWBhAAAAABJRU5ErkJggg==" } }, "alt": "Page wireframe", "width": function () { if (this.ParentName != null) { return "65%" } else { return "1%" } }, "height": "auto" }
}, { "<>": "img", "src": function () { if (this.ParentName != null) { return "PBIInspectorPNG\\" + this.Id + ".png" } else { return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAAAxJREFUGFdj+P//PwAF/gL+pzWBhAAAAABJRU5ErkJggg==" } }, "alt": "Page wireframe", "width": function () { if (this.ParentName != null) { return "65%" } else { return "1%" } }, "height": function () { if (this.ParentName != null) { return "65%" } else { return "1%" } } }
]
}
]
58 changes: 39 additions & 19 deletions PBIXInspectorWinLibrary/Main.cs
Original file line number Diff line number Diff line change
@@ -39,6 +39,26 @@ private set
}
}

public static void Run(string pbiFilePath, string rulesFilePath, string outputPath, bool verbose, bool jsonOutput, bool htmlOutput)
{
var formatsString = string.Concat(jsonOutput ? "JSON" : string.Empty, ",", htmlOutput ? "HTML" : string.Empty);
var verboseString = verbose.ToString();

string resolvedPbiFilePath = string.Empty;

try
{
resolvedPbiFilePath = ArgsUtils.ResolvePbiFilePathInput(pbiFilePath);
}
catch (ArgumentException e)
{
OnMessageIssued(MessageTypeEnum.Error, e.Message);
}

var args = new Args { PBIFilePath = resolvedPbiFilePath, RulesFilePath = rulesFilePath, OutputPath = outputPath, FormatsString = formatsString, VerboseString = verboseString };

Run(args);
}

public static void Run(Args args)
{
@@ -54,12 +74,12 @@ public static void Run(Args args)

try
{
_insp = new Inspector(_args.PBIFilePath, _args.RulesFilePath);
_insp = new Inspector(Main._args.PBIFilePath, Main._args.RulesFilePath);
_insp.MessageIssued += Insp_MessageIssued;

_testResults = _insp.Inspect().Where(_ => (!_args.Verbose && !_.Pass) || (_args.Verbose));
_testResults = _insp.Inspect().Where(_ => (!Main._args.Verbose && !_.Pass) || (Main._args.Verbose));

if (_args.CONSOLEOutput || _args.ADOOutput)
if (Main._args.CONSOLEOutput || Main._args.ADOOutput)
{
foreach (var result in _testResults)
{
@@ -70,43 +90,43 @@ public static void Run(Args args)
}

//Ensure output dir exists
if (!_args.ADOOutput && (_args.JSONOutput || _args.HTMLOutput || _args.PNGOutput))
if (!Main._args.ADOOutput && (Main._args.JSONOutput || Main._args.HTMLOutput || Main._args.PNGOutput))
{
if (!Directory.Exists(_args.OutputDirPath))
if (!Directory.Exists(Main._args.OutputDirPath))
{
Directory.CreateDirectory(_args.OutputDirPath);
Directory.CreateDirectory(Main._args.OutputDirPath);
}
}

if (!_args.ADOOutput && (_args.JSONOutput || _args.HTMLOutput))
if (!Main._args.ADOOutput && (Main._args.JSONOutput || Main._args.HTMLOutput))
{
var outputFilePath = string.Empty;
var pbiFileNameWOextension = Path.GetFileNameWithoutExtension(_args.PBIFilePath);
var pbiFileNameWOextension = Path.GetFileNameWithoutExtension(Main._args.PBIFilePath);

if (!string.IsNullOrEmpty(_args.OutputDirPath))
if (!string.IsNullOrEmpty(Main._args.OutputDirPath))
{
outputFilePath = Path.Combine(_args.OutputDirPath, string.Concat("TestRun_", pbiFileNameWOextension, ".json"));
outputFilePath = Path.Combine(Main._args.OutputDirPath, string.Concat("TestRun_", pbiFileNameWOextension, ".json"));
}
else
{
throw new ArgumentException("Directory with path \"{0}\" does not exist", _args.OutputDirPath);
throw new ArgumentException("Directory with path \"{0}\" does not exist", Main._args.OutputDirPath);
}

var testRun = new TestRun() { CompletionTime = DateTime.Now, TestedFilePath = _args.PBIFilePath, RulesFilePath = _args.RulesFilePath, Verbose = _args.Verbose, Results = _testResults };
var testRun = new TestRun() { CompletionTime = DateTime.Now, TestedFilePath = Main._args.PBIFilePath, RulesFilePath = Main._args.RulesFilePath, Verbose = Main._args.Verbose, Results = _testResults };
_jsonTestRun = JsonSerializer.Serialize(testRun);
if (_args.JSONOutput)
if (Main._args.JSONOutput)
{
OnMessageIssued(MessageTypeEnum.Information, string.Format("Writing JSON output to file at \"{0}\".", outputFilePath));
File.WriteAllText(outputFilePath, _jsonTestRun, System.Text.Encoding.UTF8);
}
}

if (!_args.ADOOutput && (_args.PNGOutput || _args.HTMLOutput))
if (!Main._args.ADOOutput && (Main._args.PNGOutput || Main._args.HTMLOutput))
{
_fieldMapInsp = new Inspector(_args.PBIFilePath, Constants.ReportPageFieldMapFilePath);
_fieldMapInsp = new Inspector(Main._args.PBIFilePath, Constants.ReportPageFieldMapFilePath);
_fieldMapResults = _fieldMapInsp.Inspect();

var outputPNGDirPath = Path.Combine(_args.OutputDirPath, Constants.PNGOutputDir);
var outputPNGDirPath = Path.Combine(Main._args.OutputDirPath, Constants.PNGOutputDir);

if (Directory.Exists(outputPNGDirPath))
{
@@ -121,7 +141,7 @@ public static void Run(Args args)
ImageUtils.DrawReportPages(_fieldMapResults, _testResults, outputPNGDirPath);
}

if (!_args.ADOOutput && _args.HTMLOutput)
if (!Main._args.ADOOutput && Main._args.HTMLOutput)
{
string pbiinspectorlogobase64 = string.Concat(Constants.Base64ImgPrefix, ImageUtils.ConvertBitmapToBase64(Constants.PBIInspectorPNG));
//string nowireframebase64 = string.Concat(Base64ImgPrefix, ImageUtils.ConvertBitmapToBase64(@"Files\png\nowireframe.png"));
@@ -130,13 +150,13 @@ public static void Run(Args args)
html = html.Replace(Constants.VersionPlaceholder, AppUtils.About(), StringComparison.OrdinalIgnoreCase);
html = html.Replace(Constants.JsonPlaceholder, _jsonTestRun, StringComparison.OrdinalIgnoreCase);

var outputHTMLFilePath = Path.Combine(_args.OutputDirPath, Constants.TestRunHTMLFileName);
var outputHTMLFilePath = Path.Combine(Main._args.OutputDirPath, Constants.TestRunHTMLFileName);

OnMessageIssued(MessageTypeEnum.Information, string.Format("Writing HTML output to file at \"{0}\".", outputHTMLFilePath));
File.WriteAllText(outputHTMLFilePath, html);

//Results have been written to a temporary directory so show output to user automatically.
if (_args.DeleteOutputDirOnExit)
if (Main._args.DeleteOutputDirOnExit)
{
AppUtils.WinOpen(outputHTMLFilePath);
}
6 changes: 3 additions & 3 deletions PBIXInspectorWinLibrary/PBIXInspectorWinLibrary.csproj
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyVersion>1.9.4.0</AssemblyVersion>
<FileVersion>1.9.4.0</FileVersion>
<VersionPrefix>1.9.4</VersionPrefix>
<AssemblyVersion>1.9.5.0</AssemblyVersion>
<FileVersion>1.9.5.0</FileVersion>
<VersionPrefix>1.9.5</VersionPrefix>
</PropertyGroup>

<ItemGroup>
16 changes: 13 additions & 3 deletions PBIXInspectorWinLibrary/Utils/ArgsUtils.cs
Original file line number Diff line number Diff line change
@@ -46,10 +46,20 @@ public static Args ParseArgs(string[] args)

public static string? ResolvePbiFilePathInput(string pbiFilePath)
{
var resolvedPath = !string.IsNullOrEmpty(pbiFilePath) && pbiFilePath.ToLower().EndsWith(Constants.PBIPReportJsonFileName)
? Path.GetDirectoryName(pbiFilePath)
: pbiFilePath;
var resolvedPath = pbiFilePath;

if (!string.IsNullOrEmpty(pbiFilePath) && (pbiFilePath.ToLower().EndsWith(Constants.PBIPReportJsonFileName)))
{
resolvedPath = Path.GetDirectoryName(pbiFilePath);
}

//TODO: support PBIP file path. Need to parse the json and retrieve the report folder path.
if (!string.IsNullOrEmpty(pbiFilePath) && (pbiFilePath.ToLower().EndsWith(Constants.PBIPFileExtension)
|| pbiFilePath.ToLower().EndsWith(Constants.PBIRFileExtension)))
{
throw new ArgumentException(string.Format("PBIP and PBIR file types are not yet supported. Please specify a report.json file path instead."));
}

return resolvedPath;
}
}
12 changes: 12 additions & 0 deletions Rules/Base-rules.json
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
"codepage": 1200,
"rules": [
{
"id": "REMOVE_UNUSED_CUSTOM_VISUALS",
"name": "Remove custom visuals which are not used in the report.",
"description": "Returns an array of custom visual names to be removed if any. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -68,6 +69,7 @@
]
},
{
"id": "REDUCE_VISUALS_ON_PAGE",
"name": "Reduce the number of visible visuals on the page",
"description": "Reports a test fail if the rule's maximum number of visible visuals on the page is exceeded. By default the base rules file specifies 20 as the maximum, set the paramMaxVisualsPerPage parameter to change this. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -133,6 +135,7 @@
]
},
{
"id": "REDUCE_OBJECTS_WITHIN_VISUALS",
"name": "Reduce the number of objects within visuals",
"description": "Reports a test fail if the rule's maximum number of objects within visuals on a page is exceeded. An object is a data field that is assigned to a visual. By default, the base rules file specifies 6 as the maximum objects within a visual. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -176,6 +179,7 @@
]
},
{
"id": "REDUCE_TOPN_FILTERS",
"name": "Reduce usage of TopN filtering visuals by page",
"description": "Reports a test fail if the rule's maximum number of visuals using TopN filtering on a the page is exceeded. By default the base rules file specifies 4 as the maximum objects within a visual, set the paramMaxTopNFilteringPerPage to change this. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -227,6 +231,7 @@
]
},
{
"id": "REDUCE_ADVANCED_FILTERS",
"name": "Reduce usage of Advanced filtering visuals by page",
"description": "Reports a test fail if the rule's maximum number of visuals using Advanced filtering on a the page is exceeded. By default, the base rules file specifies 4 as the maximum objects within a visual, set the paramMaxAdvancedFilteringVisualsPerPage parameter value to change this. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -278,6 +283,7 @@
]
},
{
"id": "REDUCE_PAGES",
"name": "Reduce number of pages per report",
"description": "Reports a test fail if the rule's maximum number of pages per report is exceeded. By default, the base rules file specifies 10 as the maximum number of pages. Set the paramMaxNumberOfPagesPerReport parameter to change this. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -307,6 +313,7 @@
]
},
{
"id": "AVOID_SHOW_ITEMS_WITH_NO_DATA",
"name": "Avoid setting ‘Show items with no data’ on columns",
"description": "Returns an array of visual names which have the option ‘Show items with no data’ enabled on one or more columns. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -357,6 +364,7 @@
]
},
{
"id": "HIDE_TOOLTIP_DRILLTROUGH_PAGES",
"name": "Tooltip and Drillthrough pages should be hidden",
"description": "Reports a test fail if a page of type Tooltip or Drillthrough is visible. To disable this rule, mark it as disabled in the base rules file.",
"logType": "warning",
@@ -404,6 +412,7 @@
]
},
{
"id": "ENSURE_THEME_COLOURS",
"name": "Ensure charts use theme colours",
"description": "Check that charts (excluding textboxes) avoid custom colours and use theme colours instead. To disable this rule, mark it as disabled in the base rules file.",
"disabled": false,
@@ -465,6 +474,7 @@
]
},
{
"id": "ENSURE_PAGES_DO_NOT_SCROLL_VERTICALLY",
"name": "Ensure pages do not scroll vertically",
"description": "Returns an array of visible page names with a height great than 720px. Modify the rule parameter value if a different maximum height value is required. To disable this rule, mark it as disabled in the base rules file.",
"disabled": false,
@@ -516,6 +526,7 @@
]
},
{
"id": "ENSURE_ALTTEXT",
"name": "Ensure alternativeText has been defined for all visuals",
"description": "Alt-text is required for screen readers",
"disabled": true,
@@ -585,6 +596,7 @@
]
},
{
"id": "template",
"name": "Rule Template",
"description": "Rule template",
"disabled": true,

0 comments on commit baead10

Please sign in to comment.