diff --git a/.vscode/launch.json b/.vscode/launch.json
index 49c9bd20..d95867a5 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -31,8 +31,8 @@
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
- "AYANOVA_LOG_LEVEL": "Info",
- //"AYANOVA_LOG_LEVEL": "Debug",
+ //"AYANOVA_LOG_LEVEL": "Info",
+ "AYANOVA_LOG_LEVEL": "Debug",
"AYANOVA_DEFAULT_LANGUAGE": "en",
//LOCALE MUST BE en for Integration TESTING
//"AYANOVA_PERMANENTLY_ERASE_DATABASE": "true",
diff --git a/devdocs/specs/core-list-graph-datatable-filtering-paging.txt b/devdocs/specs/core-list-graph-datatable-filtering-paging.txt
index 96ff6e4c..c3791deb 100644
--- a/devdocs/specs/core-list-graph-datatable-filtering-paging.txt
+++ b/devdocs/specs/core-list-graph-datatable-filtering-paging.txt
@@ -63,9 +63,7 @@ LIST FILTERING TODO
Add test for providing saved filter id to widgetlist and recieving correct filtered / paged / sorted results (test all those)
- Requires filter to sql code to be written and changes to the widgetlist route
- - This is where it gets real! Happy monday!! :)
- - Copy as much from v7 as possible
-
+
- Client side
- Implement filter editor dialog and test
diff --git a/devdocs/todo.txt b/devdocs/todo.txt
index 07f7ed97..d3582d69 100644
--- a/devdocs/todo.txt
+++ b/devdocs/todo.txt
@@ -12,7 +12,10 @@ SERVER SCHEMA
- See datafilter schema code (note that it will increase the index count by one)
- http://www.postgresqltutorial.com/postgresql-unique-constraint/
-
+SERVER ALL LIST ROUTES
+ - Forgot to put AsNoTracking in the list routes, no need to track them as they are only returning info, not being modified
+ - See widgetBiz getmany method for example
+ - This applies to all picklist and list routes
INITIAL TESTING NOTES:
- Tested on iPad (safari, chrome), Pixel (chrome), samsung chrome and samsung browser, desktop chrome, firefox browsers, android table (looks good, works but very slow, good for testing)
diff --git a/server/AyaNova/ControllerHelpers/PagingOptions.cs b/server/AyaNova/ControllerHelpers/PagingOptions.cs
index d9517aa7..a5adf334 100644
--- a/server/AyaNova/ControllerHelpers/PagingOptions.cs
+++ b/server/AyaNova/ControllerHelpers/PagingOptions.cs
@@ -18,9 +18,13 @@ namespace AyaNova.Api.ControllerHelpers
[Range(1, MaxPageSize, ErrorMessage = "Limit must be greater than 0 and less than 100.")]
public int? Limit { get; set; }
- public string Sort {get;set;}
- public bool? Asc {get;set;}
-
+ public string Sort { get; set; }
+ public bool? Asc { get; set; }
+
+ //Data filter id to use with this list query
+ //0 or less means no filter
+ public long DataFilterId { get; set; }
+
}
diff --git a/server/AyaNova/Startup.cs b/server/AyaNova/Startup.cs
index e42f09a9..f34e0cf8 100644
--- a/server/AyaNova/Startup.cs
+++ b/server/AyaNova/Startup.cs
@@ -98,7 +98,7 @@ namespace AyaNova
bool LOG_SENSITIVE_DATA = false;
#if (DEBUG)
- //LOG_SENSITIVE_DATA = true;
+ LOG_SENSITIVE_DATA = true;
#endif
diff --git a/server/AyaNova/biz/IFilterableObject.cs b/server/AyaNova/biz/IFilterableObject.cs
deleted file mode 100644
index 12d34cbd..00000000
--- a/server/AyaNova/biz/IFilterableObject.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using AyaNova.Models;
-using Newtonsoft.Json.Linq;
-
-
-namespace AyaNova.Biz
-{
- ///
- /// Interface for biz objects that support list filtering
- ///
- internal interface IFilterableObject
- {
-
- //FILTEROPTIONS COLLECTION
- FilterOptions FilterOptions { get; }
-
-
- }
-
-}
\ No newline at end of file
diff --git a/server/AyaNova/biz/WidgetBiz.cs b/server/AyaNova/biz/WidgetBiz.cs
index 21cde0df..5dc8974a 100644
--- a/server/AyaNova/biz/WidgetBiz.cs
+++ b/server/AyaNova/biz/WidgetBiz.cs
@@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace AyaNova.Biz
{
-
+
internal class WidgetBiz : BizObject, IJobObject
{
public static FilterOptions FilterOptions(long localizeToLocaleId = 0)
@@ -153,14 +153,39 @@ namespace AyaNova.Biz
internal async Task> GetManyAsync(IUrlHelper Url, string routeName, PagingOptions pagingOptions)
{
+ //TODO: build a query, run it outside of Entity Framework directly and return the results in models?!
+ //Or can ef accept a query with criteria
+ //https://docs.microsoft.com/en-us/ef/core/querying/raw-sql
+ //If no datafilter then it can just run this regular code which already works, I think?
+ //no, order by is brokeh
+
+
pagingOptions.Offset = pagingOptions.Offset ?? PagingOptions.DefaultOffset;
pagingOptions.Limit = pagingOptions.Limit ?? PagingOptions.DefaultLimit;
+ // var items = await ct.Widget
+ // .OrderBy(m => m.Id)
+ // .Skip(pagingOptions.Offset.Value)
+ // .Take(pagingOptions.Limit.Value)
+ // .ToArrayAsync();
+
+
+ //BUILD THE QUERY
+ //base query
+ var q = "SELECT *, xmin FROM AWIDGET ";
+
+ //BUILD WHERE AND APPEND IT
+
+ //BUILD ORDER BY AND APPEND IT
+
+
var items = await ct.Widget
- .OrderBy(m => m.Id)
- .Skip(pagingOptions.Offset.Value)
- .Take(pagingOptions.Limit.Value)
- .ToArrayAsync();
+ .AsNoTracking()
+ .FromSql(q)
+ .Skip(pagingOptions.Offset.Value)
+ .Take(pagingOptions.Limit.Value)
+ .ToArrayAsync();
+
var totalRecordCount = await ct.Widget.CountAsync();
var pageLinks = new PaginationLinkBuilder(Url, routeName, null, pagingOptions, totalRecordCount).PagingLinksObject();
@@ -314,11 +339,11 @@ namespace AyaNova.Biz
//run validation and biz rules
if (isNew)
{
- //NEW widgets must be active
- if (inObj.Active == null || ((bool)inObj.Active) == false)
- {
- AddError(ValidationErrorType.InvalidValue, "Active", "New widget must be active");
- }
+ // //NEW widgets must be active
+ // if (inObj.Active == null || ((bool)inObj.Active) == false)
+ // {
+ // AddError(ValidationErrorType.InvalidValue, "Active", "New widget must be active");
+ // }
}
//OwnerId required
diff --git a/test/raven-integration/DataFilter/DataFilterFilteringLists.cs b/test/raven-integration/DataFilter/DataFilterFilteringLists.cs
new file mode 100644
index 00000000..eee48900
--- /dev/null
+++ b/test/raven-integration/DataFilter/DataFilterFilteringLists.cs
@@ -0,0 +1,196 @@
+using System;
+using Xunit;
+using Newtonsoft.Json.Linq;
+using FluentAssertions;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+
+namespace raven_integration
+{
+
+
+ /*
+
+ Using the widget object test all filtering options
+ for all data types, all operation types
+
+ This is the supertest to always confirm the filtering code is working as expected.
+
+
+ */
+
+ public class DataFilterFilteringLists
+ {
+ public const string OpEquality = "=";
+ public const string OpGreaterThan = ">";
+ public const string OpGreaterThanOrEqualTo = ">=";
+ public const string OpLessThan = "<";
+ public const string OpLessThanOrEqualTo = "<=";
+ public const string OpNotEqual = "!=";
+ public const string OpNotLike = "!%";
+ public const string OpStartsWith = "%-";
+ public const string OpEndsWith = "-%";
+ public const string OpContains = "-%-";
+ public const string OpNotContains = "!-%-";
+
+ // public const string AyDataTypeDate = "date";
+ // public const string AyDataTypeText = "text";
+ // public const string AyDataTypeInteger = "int";
+ // public const string AyDataTypeBool = "bool";
+ // public const string AyDataTypeDecimal = "decimal";
+
+
+
+ ///////////////////////
+ //DATE
+ //
+
+ ///////////////////////
+ //TEXT
+ //
+
+ ///////////////////////
+ //INT
+ //
+
+ ///////////////////////
+ //BOOL
+ //
+
+
+
+ ///
+ ///
+ ///
+ [Fact]
+ public async void BoolFiltersWork()
+ {
+
+ //OPS: equal to, not equal to
+ //values: true, false
+
+ var WidgetNameStart = "BoolDataFilterTest";
+
+ List ActiveWidgetIdList = new List();
+ List NotActiveWidgetIdList = new List();
+
+ //CREATE 4 TEST WIDGETS
+ //two active and two non active
+
+ //first active widget
+ dynamic w = new JObject();
+ w.name = Util.Uniquify(WidgetNameStart);
+ w.active = true;
+ w.roles = 0;
+
+ ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString());
+ Util.ValidateDataReturnResponseOk(a);
+ ActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value());
+
+ //second active widget
+ w.name = Util.Uniquify(WidgetNameStart);
+
+ a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString());
+ Util.ValidateDataReturnResponseOk(a);
+ ActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value());
+
+
+ //first NON active widget
+ w.name = Util.Uniquify(WidgetNameStart);
+ w.active = false;
+
+ a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString());
+ Util.ValidateDataReturnResponseOk(a);
+ NotActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value());
+
+ //second NON active widget
+ w.name = Util.Uniquify(WidgetNameStart);
+ w.active = false;
+
+ a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString());
+ Util.ValidateDataReturnResponseOk(a);
+ NotActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value());
+
+
+ //CREATE FILTER
+ dynamic d = new JObject();
+ d.name = Util.Uniquify("Test BOOL DataFilter");
+ // d.ownerId = 1L;
+ d["public"] = true;
+ d.listKey = "widget";
+
+ dynamic dfilter = new JArray();
+
+ //name starts with filter to constrict to widgets that this test block created only
+ dynamic DataFilterNameStart = new JObject();
+ DataFilterNameStart.fld = "name";
+ DataFilterNameStart.op = OpStartsWith;
+ DataFilterNameStart.value = WidgetNameStart;
+ dfilter.Add(DataFilterNameStart);
+
+ //active bool test filter
+ dynamic DataFilterActive = new JObject();
+ DataFilterActive.fld = "active";
+ DataFilterActive.op = OpEquality;
+ DataFilterActive.value = true;
+ dfilter.Add(DataFilterActive);
+
+ d.filter = dfilter.ToString();//it expects it to be a json string, not actual json
+
+ a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString());
+ Util.ValidateDataReturnResponseOk(a);
+
+ long DataFilterId = a.ObjectResponse["data"]["id"].Value();
+
+ //NOW FETCH WIDGET LIST WITH FILTER
+ a = await Util.GetAsync($"Widget/listwidgets?DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in"));
+ Util.ValidateDataReturnResponseOk(a);
+ Util.ValidateHTTPStatusCode(a, 200);
+
+ //assert aAll contains exactly two records
+ ((JArray)a.ObjectResponse["data"]).Count.Should().Be(2);
+
+//TODO: ensure the results match the appropriate matching widgetIDList made earlier
+
+
+
+ // //UPDATE FILTER TO LIMIT TO INACTIVE
+
+ // //PUT, make private
+ // d["public"] = false;
+ // d.name = Util.Uniquify("Put - Test DataFilter (privatized)");
+ // d.concurrencyToken = a.ObjectResponse["data"]["concurrencyToken"].Value();
+ // a = await Util.PutAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull"), d.ToString());
+ // Util.ValidateHTTPStatusCode(a, 200);
+
+ // //check PUT worked
+ // a = await Util.GetAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull"));
+ // Util.ValidateNoErrorInResponse(a);
+ // a.ObjectResponse["data"]["name"].Value().Should().Be(d.name.ToString());
+
+
+ // //FETCH DISALLOWED
+ // //Get as alternate user should fail for private filter
+ // a = await Util.GetAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("SubContractorLimited"));
+ // Util.ValidateResponseNotFound(a);
+
+ // // //DELETE
+ // ApiResponse DELETETestResponse = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull"));
+ // Util.ValidateHTTPStatusCode(DELETETestResponse, 204);
+
+ }
+
+
+
+ ///////////////////////
+ //DECIMAL
+ //
+
+ ///////////////////////
+ //TAGS
+ //
+
+ //==================================================
+
+ }//eoc
+}//eons
diff --git a/test/raven-integration/Widget/WidgetLists.cs b/test/raven-integration/Widget/WidgetLists.cs
index 8bb86d39..01e6736f 100644
--- a/test/raven-integration/Widget/WidgetLists.cs
+++ b/test/raven-integration/Widget/WidgetLists.cs
@@ -11,7 +11,7 @@ namespace raven_integration
public class WidgetLists
{
-
+
///
///
///
@@ -26,11 +26,11 @@ namespace raven_integration
d.active = true;
d.roles = 0;
- ApiResponse r1 = await Util.PostAsync("Widget", await Util.GetTokenAsync( "manager", "l3tm3in"), d.ToString());
+ ApiResponse r1 = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString());
Util.ValidateDataReturnResponseOk(r1);
//Get all
- ApiResponse a = await Util.GetAsync("Widget/picklist?Offset=2&Limit=3&q=%25of%25", await Util.GetTokenAsync( "InventoryLimited"));
+ ApiResponse a = await Util.GetAsync("Widget/picklist?Offset=2&Limit=3&q=%25of%25", await Util.GetTokenAsync("InventoryLimited"));
Util.ValidateDataReturnResponseOk(a);
Util.ValidateHTTPStatusCode(a, 200);
((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0);
@@ -44,7 +44,7 @@ namespace raven_integration
public async void PagingShouldWorkAsExpected()
{
//Get all
- ApiResponse a = await Util.GetAsync("Widget/listwidgets?Offset=2&Limit=3", await Util.GetTokenAsync( "manager", "l3tm3in"));
+ ApiResponse a = await Util.GetAsync("Widget/listwidgets?Offset=2&Limit=3", await Util.GetTokenAsync("manager", "l3tm3in"));
Util.ValidateDataReturnResponseOk(a);
Util.ValidateHTTPStatusCode(a, 200);
@@ -64,7 +64,7 @@ namespace raven_integration
-///
+ ///
///
///
[Fact]
@@ -99,6 +99,7 @@ namespace raven_integration
+
//==================================================
}//eoc
diff --git a/test/raven-integration/Widget/WidgetValidationTests.cs b/test/raven-integration/Widget/WidgetValidationTests.cs
index 619963e1..e3bd09bd 100644
--- a/test/raven-integration/Widget/WidgetValidationTests.cs
+++ b/test/raven-integration/Widget/WidgetValidationTests.cs
@@ -11,27 +11,27 @@ namespace raven_integration
{
- ///
- /// Test business rule should be active on new
- ///
- [Fact]
- public async void BusinessRuleNewShouldBeActiveShouldWork()
- {
- //CREATE attempt with broken rules
- dynamic d = new JObject();
- d.name = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToDeleteNonOwned TEST WIDGET");
- d.created = DateTime.Now.ToString();
- d.dollarAmount = 1.11m;
- d.active = false;//<--- BROKEN RULE new widget must be active = true!!
- d.roles = 0;
+ // ///
+ // /// Test business rule should be active on new
+ // ///
+ // [Fact]
+ // public async void BusinessRuleNewShouldBeActiveShouldWork()
+ // {
+ // //CREATE attempt with broken rules
+ // dynamic d = new JObject();
+ // d.name = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToDeleteNonOwned TEST WIDGET");
+ // d.created = DateTime.Now.ToString();
+ // d.dollarAmount = 1.11m;
+ // d.active = false;//<--- BROKEN RULE new widget must be active = true!!
+ // d.roles = 0;
- //create via inventory full test user
- ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString());
+ // //create via inventory full test user
+ // ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString());
- Util.ValidateErrorCodeResponse(a, 2200, 400);
- Util.ShouldContainValidationError(a, "Active", "InvalidValue");
+ // Util.ValidateErrorCodeResponse(a, 2200, 400);
+ // Util.ShouldContainValidationError(a, "Active", "InvalidValue");
- }
+ // }