Browse Source

Update jint (#967)

* Update jint and move some files.

* Another update to the vars.
pull/968/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
3d3fa864c9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs
  2. 18
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  3. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintExtensions.cs
  4. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs
  5. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintUser.cs
  6. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs
  7. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  8. 67
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs
  9. 19
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs
  10. 13
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs
  11. 12
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs
  12. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  13. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs
  14. 7
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  15. 8
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs
  16. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs
  17. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs
  18. 4
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/AllContentsByPostDto.cs
  19. 54
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs
  20. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs
  21. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs

@ -18,7 +18,7 @@ public sealed class ContentDataProperty : CustomProperty
private ContentFieldObject? contentField; private ContentFieldObject? contentField;
private JsValue value; private JsValue value;
protected override JsValue CustomValue protected override JsValue? CustomValue
{ {
get get
{ {

18
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -16,14 +16,24 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
public static class JsonMapper public static class JsonMapper
{ {
private class JsonObjectInstance : ObjectInstance
{
public JsonObjectInstance(Engine engine)
: base(engine)
{
}
}
public static JsValue Map(JsonValue value, Engine engine) public static JsValue Map(JsonValue value, Engine engine)
{ {
switch (value.Value) switch (value.Value)
{ {
case null: case null:
return JsValue.Null; return JsValue.Null;
case bool b: case true:
return new JsBoolean(b); return JsBoolean.True;
case false:
return JsBoolean.False;
case double n: case double n:
return new JsNumber(n); return new JsNumber(n);
case string s: case string s:
@ -52,11 +62,11 @@ public static class JsonMapper
private static JsValue FromObject(JsonObject obj, Engine engine) private static JsValue FromObject(JsonObject obj, Engine engine)
{ {
var target = new ObjectInstance(engine); var target = new JsonObjectInstance(engine);
foreach (var (key, value) in obj) foreach (var (key, value) in obj)
{ {
target.FastAddProperty(key, Map(value, engine), true, true, true); target.Set(key, Map(value, engine));
} }
return target; return target;

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintExtensions.cs → backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintExtensions.cs

@ -9,7 +9,7 @@ using Jint;
using Jint.Native; using Jint.Native;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting.Internal;
public static class JintExtensions public static class JintExtensions
{ {

8
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/DefaultConverter.cs → backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs

@ -16,13 +16,13 @@ using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting.Internal;
public sealed class DefaultConverter : IObjectConverter public sealed class JintObjectConverter : IObjectConverter
{ {
public static readonly DefaultConverter Instance = new DefaultConverter(); public static readonly JintObjectConverter Instance = new JintObjectConverter();
private DefaultConverter() private JintObjectConverter()
{ {
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs → backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintUser.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure.Security;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting.Internal;
public static class JintUser public static class JintUser
{ {

8
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs

@ -14,10 +14,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.Internal;
internal sealed class Parser internal sealed class Parser
{ {
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
private static readonly ParserOptions DefaultParserOptions = new ParserOptions
{
AdaptRegexp = true, Tolerant = true
};
private readonly IMemoryCache cache; private readonly IMemoryCache cache;
@ -34,9 +30,7 @@ internal sealed class Parser
{ {
entry.AbsoluteExpirationRelativeToNow = CacheDuration; entry.AbsoluteExpirationRelativeToNow = CacheDuration;
var parser = new JavaScriptParser(script, DefaultParserOptions); return new JavaScriptParser().ParseScript(script);
return parser.ParseScript();
})!; })!;
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -133,7 +133,7 @@ public sealed class JintScriptEngine : IScriptEngine, IScriptDescriptor
var engine = new Engine(engineOptions => var engine = new Engine(engineOptions =>
{ {
engineOptions.AddObjectConverter(DefaultConverter.Instance); engineOptions.AddObjectConverter(JintObjectConverter.Instance);
engineOptions.SetReferencesResolver(NullPropagation.Instance); engineOptions.SetReferencesResolver(NullPropagation.Instance);
engineOptions.Strict(); engineOptions.Strict();

67
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs

@ -5,16 +5,65 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Text;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
public class ScriptContext : Dictionary<string, object?> public class ScriptContext : IEnumerable<KeyValuePair<string, (object? Value, bool IsReadonly)>>
{ {
public ScriptContext() private readonly Dictionary<string, (object? Value, bool IsReadonly)> values = new Dictionary<string, (object? Value, bool IsReadonly)>(StringComparer.OrdinalIgnoreCase);
: base(StringComparer.OrdinalIgnoreCase)
public void CopyFrom(ScriptVars vars)
{
Guard.NotNull(vars);
foreach (var (key, item) in vars)
{
if (!values.ContainsKey(key))
{
SetItem(key, item);
}
}
}
public void SetItem(string? key, (object? Value, bool IsReadonly) item)
{
Set(key, item.Value, item.IsReadonly);
}
public void Set(string? key, object? value, bool isReadonly = false)
{ {
if (string.IsNullOrWhiteSpace(key))
{
return;
}
var finalKey = key.ToCamelCase();
if (values.TryGetValue(finalKey, out var existing) && existing.IsReadonly)
{
return;
}
values[finalKey] = (value, isReadonly);
}
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value)
{
Guard.NotNull(key);
value = default!;
if (values.TryGetValue(key, out var item))
{
value = item.Value;
return true;
}
return false;
} }
public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value) public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value)
@ -23,7 +72,7 @@ public class ScriptContext : Dictionary<string, object?>
value = default!; value = default!;
if (TryGetValue(key, out var temp) && temp is T typed) if (values.TryGetValue(key, out var item) && item.Value is T typed)
{ {
value = typed; value = typed;
return true; return true;
@ -31,4 +80,14 @@ public class ScriptContext : Dictionary<string, object?>
return false; return false;
} }
public IEnumerator<KeyValuePair<string, (object? Value, bool IsReadonly)>> GetEnumerator()
{
return values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return values.GetEnumerator();
}
} }

19
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs

@ -7,7 +7,6 @@
using Jint; using Jint;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Text;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
@ -117,30 +116,20 @@ public sealed class ScriptExecutionContext<T> : ScriptExecutionContext, ISchedul
{ {
var engine = Engine; var engine = Engine;
CopyFrom(vars);
if (options.AsContext) if (options.AsContext)
{ {
var contextInstance = new WritableContext(engine, vars); var contextInstance = new WritableContext(engine, vars);
foreach (var (key, value) in vars.Where(x => x.Value != null))
{
this[key.ToCamelCase()] = value;
}
engine.SetValue("ctx", contextInstance); engine.SetValue("ctx", contextInstance);
engine.SetValue("context", contextInstance); engine.SetValue("context", contextInstance);
} }
else else
{ {
foreach (var (key, value) in vars) foreach (var (key, item) in vars)
{ {
var property = key.ToCamelCase(); engine.SetValue(key, item.Value!);
if (value != null)
{
engine.SetValue(property, value);
this[property] = value;
}
} }
} }

13
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs

@ -11,12 +11,19 @@ namespace Squidex.Domain.Apps.Core.Scripting;
public class ScriptVars : ScriptContext public class ScriptVars : ScriptContext
{ {
public void SetValue(object? value, [CallerMemberName] string? key = null) public object? this[string key]
{ {
if (key != null) get
{ {
this[key] = value; TryGetValue(key, out var result);
return result;
} }
set => Set(key, value, true);
}
public void SetValue(object? value, [CallerMemberName] string? key = null)
{
Set(key, value, true);
} }
public T GetValue<T>([CallerMemberName] string? key = null) public T GetValue<T>([CallerMemberName] string? key = null)

12
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs

@ -8,7 +8,6 @@
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Jint.Native.Object; using Jint.Native.Object;
using Squidex.Text;
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
@ -21,14 +20,9 @@ internal sealed class WritableContext : ObjectInstance
{ {
this.vars = vars; this.vars = vars;
foreach (var (key, value) in vars) foreach (var (key, item) in vars)
{ {
var property = key.ToCamelCase(); base.Set(key, FromObject(engine, item.Value), this);
if (value != null)
{
FastAddProperty(property, FromObject(engine, value), true, true, true);
}
} }
} }
@ -36,7 +30,7 @@ internal sealed class WritableContext : ObjectInstance
{ {
var propertyName = property.AsString(); var propertyName = property.AsString();
vars[propertyName] = value.ToObject(); vars.Set(propertyName, value.ToObject());
return base.Set(property, value, receiver); return base.Set(property, value, receiver);
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -20,7 +20,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Fluid.Core" Version="2.3.0" /> <PackageReference Include="Fluid.Core" Version="2.3.0" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" /> <PackageReference Include="GeoJSON.Net" Version="1.2.19" />
<PackageReference Include="Jint" Version="3.0.0-beta-2036" /> <PackageReference Include="Jint" Version="3.0.0-beta-2046" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.756"> <PackageReference Include="Meziantou.Analyzer" Version="1.0.756">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

2
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Billing; using Squidex.Domain.Apps.Entities.Billing;
using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.Teams;
@ -14,7 +15,6 @@ using Squidex.Infrastructure.Validation;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
using Squidex.Text; using Squidex.Text;
using System.Security.Claims;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;

7
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs

@ -15,6 +15,7 @@ using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Properties; using Squidex.Domain.Apps.Entities.Properties;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -180,14 +181,14 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
if (componentY?.IsNumber() == true) if (componentY?.IsNumber() == true)
{ {
options.ComponentX = (int)componentX.AsNumber(); options.ComponentX = (int)componentY.AsNumber();
} }
var assetThumbnailGenerator = serviceProvider.GetRequiredService<IAssetThumbnailGenerator>(); var assetGenerator = serviceProvider.GetRequiredService<IAssetThumbnailGenerator>();
var assetFileStore = serviceProvider.GetRequiredService<IAssetFileStore>(); var assetFileStore = serviceProvider.GetRequiredService<IAssetFileStore>();
try try
{ {
var hash = await asset.GetBlurHashAsync(options, assetFileStore, assetThumbnailGenerator, ct); var hash = await asset.GetBlurHashAsync(options, assetFileStore, assetGenerator, ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, hash)); scheduler.Run(callback, JsValue.FromObject(context.Engine, hash));
} }

8
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs

@ -82,13 +82,7 @@ public sealed class ScriptAsset : IAssetEnricherStep
} }
}; };
foreach (var (key, value) in sharedVars) vars.CopyFrom(sharedVars);
{
if (!vars.ContainsKey(key))
{
vars[key] = value;
}
}
var options = new ScriptOptions var options = new ScriptOptions
{ {

8
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs

@ -79,13 +79,7 @@ public sealed class ScriptContent : IContentEnricherStep
StatusOld = default StatusOld = default
}; };
foreach (var (key, value) in sharedVars) vars.CopyFrom(sharedVars);
{
if (!vars.ContainsKey(key))
{
vars[key] = value;
}
}
var options = new ScriptOptions var options = new ScriptOptions
{ {

1
backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs

@ -10,6 +10,7 @@ using Jint.Native;
using Jint.Runtime; using Jint.Runtime;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Properties; using Squidex.Domain.Apps.Entities.Properties;
using Squidex.Infrastructure; using Squidex.Infrastructure;

4
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/AllContentsByPostDto.cs

@ -5,14 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Text.Json;
using System.Text.Json.Serialization;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Squidex.Areas.Api.Controllers.Contents.Models; namespace Squidex.Areas.Api.Controllers.Contents.Models;

54
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs

@ -488,6 +488,40 @@ public class JintScriptEngineTests : IClassFixture<TranslationsFixture>
Assert.Equal(id.ToString(), actual.ToString()); Assert.Equal(id.ToString(), actual.ToString());
} }
[Fact]
public void Should_allow_null_vars()
{
var vars = new ScriptVars
{
["value"] = null
};
const string script = @"
return value;
";
var actual = sut.Execute(vars, script);
Assert.Equal(JsonValue.Null, actual);
}
[Fact]
public void Should_not_allow_to_overwrite_initial_var()
{
var vars = new ScriptVars
{
["value"] = 13
};
const string script = @"
ctx.value = ctx.value * 2;
";
sut.Execute(vars, script, new ScriptOptions { AsContext = true });
Assert.Equal(13, vars["value"]);
}
[Fact] [Fact]
public void Should_share_vars_between_executions() public void Should_share_vars_between_executions()
{ {
@ -497,11 +531,11 @@ public class JintScriptEngineTests : IClassFixture<TranslationsFixture>
}; };
const string script1 = @" const string script1 = @"
ctx.value = ctx.value * 2; ctx.shared = ctx.value * 2;
"; ";
const string script2 = @" const string script2 = @"
return ctx.value + 2; return ctx.shared + 2;
"; ";
sut.Execute(vars, script1, new ScriptOptions { AsContext = true }); sut.Execute(vars, script1, new ScriptOptions { AsContext = true });
@ -543,30 +577,22 @@ public class JintScriptEngineTests : IClassFixture<TranslationsFixture>
}; };
const string script1 = @" const string script1 = @"
ctx.obj = { number: ctx.value * 2 }; ctx.shared = { number: ctx.value * 2 };
"; ";
const string script2 = @" const string script2 = @"
ctx.data.test = { iv: ctx.obj.number + 2 }; ctx.data.test = { iv: ctx.shared.number + 2 };
replace(); replace();
"; ";
#pragma warning disable MA0042 // Do not use blocking calls in an async method await sut.ExecuteAsync(vars, script1, new ScriptOptions { AsContext = true });
sut.Execute(vars, script1, new ScriptOptions { AsContext = true });
#pragma warning restore MA0042 // Do not use blocking calls in an async method
var vars2 = new DataScriptVars var vars2 = new DataScriptVars
{ {
["data"] = new ContentData() ["data"] = new ContentData()
}; };
foreach (var (key, value) in vars) vars2.CopyFrom(vars);
{
if (!vars2.ContainsKey(key))
{
vars2[key] = value;
}
}
var actual = await sut.TransformAsync(vars2, script2, new ScriptOptions { AsContext = true }); var actual = await sut.TransformAsync(vars2, script2, new ScriptOptions { AsContext = true });

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs

@ -7,7 +7,7 @@
using System.Security.Claims; using System.Security.Claims;
using Jint; using Jint;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs

@ -76,7 +76,7 @@ public class ScriptContentTests : GivenContext
A<DataScriptVars>.That.Matches(x => A<DataScriptVars>.That.Matches(x =>
Equals(x["contentId"], content.Id) && Equals(x["contentId"], content.Id) &&
Equals(x["data"], oldData) && Equals(x["data"], oldData) &&
Equals(x["AppId"], AppId.Id) && Equals(x["appId"], AppId.Id) &&
Equals(x["appName"], AppId.Name) && Equals(x["appName"], AppId.Name) &&
Equals(x["user"], ApiContext.UserPrincipal)), Equals(x["user"], ApiContext.UserPrincipal)),
"my-query", "my-query",

Loading…
Cancel
Save