diff --git a/devdocs/specs/core-list-graph-datatable-filtering-paging.txt b/devdocs/specs/core-list-graph-datatable-filtering-sorting-paging.txt similarity index 81% rename from devdocs/specs/core-list-graph-datatable-filtering-paging.txt rename to devdocs/specs/core-list-graph-datatable-filtering-sorting-paging.txt index 762a3e6a..8e553973 100644 --- a/devdocs/specs/core-list-graph-datatable-filtering-paging.txt +++ b/devdocs/specs/core-list-graph-datatable-filtering-sorting-paging.txt @@ -1,4 +1,4 @@ -LIST / GRAPH FILTERING AND PAGING +LIST / GRAPH FILTERING SORTING AND PAGING This all applies equally to a report or a list or a graph so all work basically the same but will refer only to "list" for simplicity: @@ -60,19 +60,9 @@ NOTES ABOUT WHY I DID THE FILTEROPTIONS LIKE I DID: -------------------------------------------------- +SORTING +=-=-=-=- -LIST FILTERING TODO - - DONE test for fetching widget filter options (localized, so have two users test each for expected response) - - DONE test for excercising all of DataFilterController route including rights to non personal alternative owner etc - - DONE test for correctly validated Widget datafilter when saved or updated via datafiltercontroller (sanity check of the filter settings and options IFilterableObject implemented) - - - DONE 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 - -Add TAGS test for providing saved filter id to widgetlist WITH TAGS specified and recieving correct filtered / paged / sorted results (test all those) - - Requires TAGS filter code to be written and changes to the widgetlist route - -- Client side - - Implement filter editor dialog and test - +Sorting is done same as filtering but property on datafilter is called Sort and it's also a json Array +Fields are: "fld" which is the same column name as in a datafilter and "dir" for direction which will contain a single character either a "-" or a "+" for descending and ascending respectively diff --git a/devdocs/todo.txt b/devdocs/todo.txt index e5c5745d..70d9da58 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -95,7 +95,7 @@ TODO SERVER - rather than try to use local browser settings which is fraught with peril will need to be specified at server itself - Wherever I am currently storing time zone that's where these other settings need to be - + - TODO: Make sure private data filters get deleted with users who created them diff --git a/server/AyaNova/biz/DataFilterBiz.cs b/server/AyaNova/biz/DataFilterBiz.cs index ea2c07bf..c0ed44c8 100644 --- a/server/AyaNova/biz/DataFilterBiz.cs +++ b/server/AyaNova/biz/DataFilterBiz.cs @@ -73,7 +73,7 @@ namespace AyaNova.Biz EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); //SEARCH INDEXING - // Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name); + // Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name); return outObj; @@ -103,7 +103,7 @@ namespace AyaNova.Biz EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext); //SEARCH INDEXING - // Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name); + // Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name); return outObj; @@ -175,7 +175,7 @@ namespace AyaNova.Biz EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); //Update keywords - // Search.ProcessUpdatedObjectKeywords(UserLocaleId, dbObj.Id, BizType, dbObj.Name, dbObj.Name); + // Search.ProcessUpdatedObjectKeywords(UserLocaleId, dbObj.Id, BizType, dbObj.Name, dbObj.Name); return true; } @@ -316,6 +316,71 @@ namespace AyaNova.Biz } } + + + + //VALIDATE SORT + //Filter json must parse + if (!string.IsNullOrWhiteSpace(inObj.Sort)) + { + try + { + var v = JArray.Parse(inObj.Sort); + for (int i = 0; i < v.Count; i++) + { + var sortItem = v[i]; + if (sortItem["fld"] == null) + AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing required \"fld\" property "); + else + { + var fld = sortItem["fld"].Value(); + if (string.IsNullOrWhiteSpace(fld)) + AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, \"fld\" property is empty and required"); + + //validate the field name if we can + if (ListValidFilterOptions != null) + { + + if (!ListValidFilterOptions.Flds.Exists(x => x.Fld == fld)) + { + AddError(ValidationErrorType.InvalidValue, "Filter", $"Filter array item {i}, fld property value \"{fld}\" is not a valid value for ListKey specified"); + } + + } + } + if (sortItem["op"] == null) + AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing required \"op\" property "); + else + { + var opType = sortItem["op"].Value(); + if (!FilterComparisonOperator.Operators.Contains(opType)) + AddError(ValidationErrorType.InvalidValue, "Filter", $"Filter array item {i}, \"op\" property value of \"{opType}\" is not a valid FilterComparisonOperator type"); + } + + if (sortItem["value"] == null) + AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property "); + else + { + if (sortItem["value"].Type == JTokenType.String && string.IsNullOrWhiteSpace(sortItem["value"].Value())) + AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property "); + + if (sortItem["value"].Type == JTokenType.Array && sortItem["value"].Count() == 0) + AddError(ValidationErrorType.RequiredPropertyEmpty, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ARRAY "); + } + + + //NOTE: value of nothing, null or empty is a valid value so no checking for it here + } + } + catch (Newtonsoft.Json.JsonReaderException ex) + { + AddError(ValidationErrorType.InvalidValue, "Sort", "Sort is not valid JSON string: " + ex.Message); + + } + } + + + return; } diff --git a/server/AyaNova/biz/WidgetBiz.cs b/server/AyaNova/biz/WidgetBiz.cs index afcab80b..3bdf1164 100644 --- a/server/AyaNova/biz/WidgetBiz.cs +++ b/server/AyaNova/biz/WidgetBiz.cs @@ -201,6 +201,9 @@ namespace AyaNova.Biz //BUILD ORDER BY AND APPEND IT //TODO: Code a separate order by builder block + + + #pragma warning disable EF1000 var items = await ct.Widget diff --git a/server/AyaNova/models/DataFilter.cs b/server/AyaNova/models/DataFilter.cs index 01bc4498..8ea8e18e 100644 --- a/server/AyaNova/models/DataFilter.cs +++ b/server/AyaNova/models/DataFilter.cs @@ -20,7 +20,8 @@ namespace AyaNova.Models public bool Public { get; set; } [Required, MaxLength(255)] public string ListKey { get; set; }//max 255 characters ascii set - public string Filter { get; set; }//can be empty I guess meaning nothing selected in it + public string Filter { get; set; }//JSON fragment filter collection + public string Sort { get; set; }//JSON fragment sort collection } } diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 73a7b5cf..f1011121 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -301,7 +301,7 @@ namespace AyaNova.Util LogUpdateMessage(log); exec("CREATE TABLE adatafilter (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, public bool not null," + - "listkey varchar(255) not null, filter text, UNIQUE(name))"); + "listkey varchar(255) not null, filter text, sort text, UNIQUE(name))"); setSchemaLevel(++currentSchema); }