Skip to content

Commit

Permalink
Merge pull request #246 from shesha-io/f/datatable-inline-editing
Browse files Browse the repository at this point in the history
F/datatable inline editing
  • Loading branch information
IvanIlyichev committed May 3, 2023
2 parents a2c0d86 + 56e60e3 commit 7167cae
Show file tree
Hide file tree
Showing 328 changed files with 4,906 additions and 3,544 deletions.
129 changes: 108 additions & 21 deletions shesha-core/src/Shesha.Framework/JsonLogic/JsonLogic2LinqConverter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Abp.Dependency;
using Abp.Domain.Entities;
using Abp.Timing;
using Castle.Core;
using Newtonsoft.Json.Linq;
using Shesha.Extensions;
Expand Down Expand Up @@ -554,13 +555,116 @@ out expr
var lambda = specExpression.Body.ReplaceParameter(specExpression.Parameters.Single(), param);

return lambda;
}
case JsOperators.Now:
{
if (@operator.Arguments.Any())
throw new Exception($"{JsOperators.Now} operator doesn't support any arguments");

return Expression.Constant(Clock.Now);
}
case JsOperators.DateAdd:
{
if (@operator.Arguments.Count() != 3)
throw new Exception($"{JsOperators.DateAdd} operator require 3 arguments: date, number and datepart");

var date = ParseTree<T>(@operator.Arguments[0], param);
var number = GetAsInt64(@operator.Arguments[1]);
if (number == null)
throw new ArgumentException($"{JsOperators.DateAdd}: the `number` argument must not be null");

var datepart = GetAsString(@operator.Arguments[2]);

switch (datepart)
{
case "day":
{
var addMethod = typeof(DateTime).GetMethod(nameof(DateTime.Add), new Type[] { typeof(TimeSpan) });
var timeSpan = TimeSpan.FromDays(number.Value);
return Expression.Call(
date,
addMethod,
Expression.Constant(timeSpan)
);
}
case "week":
{
var addMethod = typeof(DateTime).GetMethod(nameof(DateTime.Add), new Type[] { typeof(TimeSpan) });
var timeSpan = TimeSpan.FromDays(number.Value * 7);
return Expression.Call(
date,
addMethod,
Expression.Constant(timeSpan)
);
}
case "month":
{
var addMonthsMethod = typeof(DateTime).GetMethod(nameof(DateTime.AddMonths), new Type[] { typeof(int) });
return Expression.Call(
date,
addMonthsMethod,
Expression.Constant(number.Value)
);
}
case "year":
{
var addYearsMethod = typeof(DateTime).GetMethod(nameof(DateTime.AddYears), new Type[] { typeof(int) });
return Expression.Call(
date,
addYearsMethod,
Expression.Constant(number.Value)
);
}
default:
throw new ArgumentException($"{JsOperators.DateAdd}: the `datepart` = `{datepart}` is not supported");
}
}
case JsOperators.Upper:
{
if (@operator.Arguments.Count() != 1)
throw new Exception($"{JsOperators.Upper} operator require 1 argument");

var arg = ParseTree<T>(@operator.Arguments[0], param);
var toUpperMethod = typeof(string).GetMethod(nameof(string.ToUpper), new Type[] { });
return Expression.Call(arg, toUpperMethod);
}
case JsOperators.Lower:
{
if (@operator.Arguments.Count() != 1)
throw new Exception($"{JsOperators.Lower} operator require 1 argument");

var arg = ParseTree<T>(@operator.Arguments[0], param);
var toLowerMethod = typeof(string).GetMethod(nameof(string.ToLower), new Type[] { });
return Expression.Call(arg, toLowerMethod);
}
default:
throw new NotSupportedException($"Operator `{@operator.Name}` is not supported");
}
}

return null;
}

private string GetAsString(JToken token)
{
if (token is JValue value)
{
return value.Value.ToString();
}
else
throw new NotSupportedException();
}

private Int64? GetAsInt64(JToken token)
{
if (token is JValue value)
{
return value.Value != null
? (Int64)value.Value
: null;
}
else
throw new NotSupportedException();
}

private bool TryCompareMemberAndDateTime(Expression left, Expression right, Func<DateTime, DateTime> dateConverter, Binder binder, out Expression expression)
Expand Down Expand Up @@ -915,27 +1019,6 @@ public bool TryGetOperator(JToken rule, out OperationProps @operator)
return true;
}

private List<string> KnownOperators = new List<string> {

JsOperators.Equal,
JsOperators.StrictEqual,
JsOperators.NotEqual,
JsOperators.StrictNotEqual,
JsOperators.Less,
JsOperators.LessOrEqual,
JsOperators.Greater,
JsOperators.GreaterOrEqual,
JsOperators.Var,
JsOperators.And,
JsOperators.Or,
JsOperators.DoubleNegotiation,
JsOperators.Negotiation,
JsOperators.Not,
JsOperators.In,
JsOperators.StartsWith,
JsOperators.EndsWith
};

/// inheritedDoc
public Expression<Func<T, bool>> ParseExpressionOf<T>(string rule)
{
Expand Down Expand Up @@ -995,6 +1078,10 @@ public static class JsOperators
public const string StartsWith = "startsWith";
public const string EndsWith = "endsWith";
public const string IsSatisfied = "is_satisfied";
public const string DateAdd = "date_add";
public const string Now = "now";
public const string Upper = "toUpperCase";
public const string Lower = "toLowerCase";
}

public class ExpressionPair
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Abp.Domain.Entities;
using Abp;
using Abp.Domain.Entities;
using Abp.Domain.Repositories;
using Abp.Linq;
using Abp.Timing;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Shesha.Authorization.Users;
Expand All @@ -9,6 +11,8 @@
using Shesha.Extensions;
using Shesha.JsonLogic;
using Shesha.Services;
using Shesha.Tests.TestingUtils;
using Shesha.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
Expand Down Expand Up @@ -1248,6 +1252,99 @@ public async Task entityReference_In_Fetch()

#endregion

#region Custom date functions

private readonly string _custom_date_funcs_Convert_expression = @"{""and"":[{""<="":[{""date_add"":[{""now"":[]},-5,""day""]},{""var"":""user.lastLoginDate""},{""now"":[]}]}]} ";

[Fact]
public void Custom_Date_Funcs_Convert()
{
using (FreezeTime())
{
var expression = ConvertToExpression<Person>(_custom_date_funcs_Convert_expression);

// todo: find a way to use start of the minute here
var now = Expression.Constant(Clock.Now).ToString();
// note: use end of minute because we skip seconds for datetime values
var nowEom = Expression.Constant(Clock.Now.EndOfTheMinute()).ToString();

var expected = $@"ent => ((Convert({now}.Add(-5.00:00:00), Nullable`1) <= ent.User.LastLoginDate) AndAlso (ent.User.LastLoginDate <= Convert({nowEom}, Nullable`1)))";
Assert.Equal(expected, expression.ToString());
}
}

[Fact]
public async Task Custom_Date_Funcs_Fetch()
{
var data = await TryFetchData<Person, Guid>(_custom_date_funcs_Convert_expression);
Assert.NotNull(data);
}

#endregion

#region Custom string functions

private readonly string _custom_string_funcs_Convert_expression = @"{
""and"": [
{
""=="": [
{
""var"": ""firstName""
},
{
""toLowerCase"": [
""TeSt""
]
}
]
},
{
""=="": [
{
""var"": ""lastName""
},
{
""toUpperCase"": [
""VaLuE""
]
}
]
},
{
""=="": [
{
""var"": ""emailAddress1""
},
{
""toLowerCase"": [
{
""var"": ""emailAddress2""
}
]
}
]
}
]
}";

[Fact]
public void Custom_String_Funcs_Convert()
{
var expression = ConvertToExpression<Person>(_custom_string_funcs_Convert_expression);

var expected = $@"ent => (((ent.FirstName == ""TeSt"".ToLower()) AndAlso (ent.LastName == ""VaLuE"".ToUpper())) AndAlso (ent.EmailAddress1 == ent.EmailAddress2.ToLower()))";
Assert.Equal(expected, expression.ToString());
}

[Fact]
public async Task Custom_String_Funcs_Fetch()
{
var data = await TryFetchData<Person, Guid>(_custom_string_funcs_Convert_expression);
Assert.NotNull(data);
}

#endregion

public class EntityWithRefListrops : Entity<Guid>
{
public virtual RefListPersonTitle Title { get; set; }
Expand All @@ -1272,5 +1369,19 @@ public class EntityWithDateProps: Entity<Guid>
public virtual int IntProp { get; set; }
public virtual int? NullableIntProp { get; set; }
}

private IDisposable FreezeTime()
{
// save current provider
var prevProvider = Clock.Provider;

// change current provider to static
Clock.Provider = new StaticClockProvider();

// return compensation logic
return new DisposeAction(() => {
Clock.Provider = prevProvider;
});
}
}
}
29 changes: 29 additions & 0 deletions shesha-core/test/Shesha.Tests/TestingUtils/StaticClockProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Abp.Timing;
using System;

namespace Shesha.Tests.TestingUtils
{
/// <summary>
/// Static clock provider, is used for unit tests only
/// It's a cope of the <see cref="UnspecifiedClockProvider"/> with just one change - after first call of <see cref="Now"/> the time stops
/// </summary>
public class StaticClockProvider : IClockProvider
{
private DateTime? _fixedTime = null;

public DateTime Now => _fixedTime ?? (DateTime)(_fixedTime = DateTime.Now);

public DateTimeKind Kind => DateTimeKind.Unspecified;

public bool SupportsMultipleTimezone => false;

public DateTime Normalize(DateTime dateTime)
{
return dateTime;
}

internal StaticClockProvider()
{
}
}
}
2 changes: 1 addition & 1 deletion shesha-core/test/Shesha.Tests/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"ConnectionStrings": {
"TestDB": "Data Source=.\\sql2019;Initial Catalog=houghton-his-test2;Integrated Security=True"
"TestDB": "Data Source=.; Initial Catalog=SheshaFunctionalTests;Integrated Security=SSPI"
}
}
Loading

0 comments on commit 7167cae

Please sign in to comment.