Browse Source

Usage triggers finalized.

pull/342/head
Sebastian Stehle 7 years ago
parent
commit
5a9310f236
  1. 6
      extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs
  2. 7
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/IUsageTrackerGrain.cs
  3. 6
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs
  4. 36
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs
  5. 2
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs
  6. 2
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs
  7. 2
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageRuleTriggerDto.cs
  8. 4
      src/Squidex/Config/Domain/EntitiesServices.cs
  9. 2
      src/Squidex/Config/Orleans/SiloWrapper.cs
  10. 1
      src/Squidex/app/features/rules/declarations.ts
  11. 2
      src/Squidex/app/features/rules/module.ts
  12. 7
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  13. 13
      src/Squidex/app/features/rules/pages/rules/triggers/usage-trigger.component.html
  14. 6
      src/Squidex/app/features/rules/pages/rules/triggers/usage-trigger.component.scss
  15. 30
      src/Squidex/app/features/rules/pages/rules/triggers/usage-trigger.component.ts
  16. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs
  17. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs

6
extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs

@ -34,14 +34,14 @@ namespace Squidex.Extensions.Actions
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'><path d='M21.875 28H6.125A6.087 6.087 0 0 1 0 21.875V6.125A6.087 6.087 0 0 1 6.125 0h15.75A6.087 6.087 0 0 1 28 6.125v15.75A6.088 6.088 0 0 1 21.875 28zM6.125 1.75A4.333 4.333 0 0 0 1.75 6.125v15.75a4.333 4.333 0 0 0 4.375 4.375h15.75a4.333 4.333 0 0 0 4.375-4.375V6.125a4.333 4.333 0 0 0-4.375-4.375H6.125z'/><path d='M21.088 23.537H9.1c-.35 0-.612-.175-.787-.525s-.088-.7.088-.962l8.225-9.713c.175-.175.438-.35.7-.35s.525.175.7.35l5.25 7.525c.088.087.088.175.088.262.438 1.225.087 2.012-.175 2.45-.613.875-1.925.963-2.1.963zm-10.063-1.75h10.15c.175 0 .612-.088.7-.262.088-.088.088-.35 0-.7l-4.55-6.475-6.3 7.438zM9.1 13.737c-2.1 0-3.85-1.75-3.85-3.85S7 6.037 9.1 6.037s3.85 1.75 3.85 3.85-1.663 3.85-3.85 3.85zm0-5.949c-1.138 0-2.1.875-2.1 2.1s.962 2.1 2.1 2.1 2.1-.962 2.1-2.1-.875-2.1-2.1-2.1z'/></svg>",
IconColor = "#3389ff",
Display = "Asset changed",
Description = "For asset changes like uploaded, updated, renamed, deleted..."
Description = "For asset changes like uploaded, updated (reuploaded), renamed, deleted..."
},
[UsageTrigger.Name] = new RuleElement
{
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M21.2 11.4c-.2 0-.4-.1-.6-.2-.5-.3-.6-.9-.3-1.4L22 7.2c.3-.5.9-.6 1.4-.3.6.4.7 1.1.4 1.5L22.1 11c-.2.3-.5.4-.9.4zM16 20.9h-.2c-1-.1-2-.6-2.5-1.5l-6-8.7c-.3-.3-.3-.8 0-1.2.3-.3.8-.4 1.2-.2l9.2 5.4c.9.5 1.5 1.4 1.6 2.4.1 1-.2 2-.9 2.8-.6.7-1.5 1-2.4 1zm-4.6-7.5l3.4 5c.2.3.6.6 1 .6s.8-.1 1.1-.4c.3-.3.4-.7.3-1.1-.1-.4-.3-.7-.6-1zM25.9 32H6.1C2.8 32 0 29.2 0 25.9v-10C0 7.1 7.1 0 15.8 0 24.8 0 32 7.2 32 16.2v9.7c0 3.3-2.8 6.1-6.1 6.1zM15.8 2C8.2 2 2 8.2 2 15.8v10C2 28.1 3.9 30 6.1 30h19.7c2.3 0 4.1-1.9 4.1-4.1v-9.7C30 8.4 23.6 2 15.8 2z'/></svg>",
IconColor = "#3389ff",
Display = "Usage limitations",
Description = "When monthly API calls exceeed a specified limit..."
Display = "Usage exceeded",
Description = "When monthly API calls exceed a specified limit for one time a month..."
}
};

7
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/IUsageTrackerGrain.cs

@ -9,17 +9,14 @@ using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
public interface IUsageTrackerGrain : IGrainWithStringKey
public interface IUsageTrackerGrain : IGrainWithStringKey, IBackgroundGrain
{
Task AddTargetAsync(Guid ruleId, NamedId<Guid> appId, int limits);
Task ActivateTargetAsync(Guid ruleId);
Task DeactivateTargetAsync(Guid ruleId);
Task RemoveTargetAsync(Guid ruleId);
Task UpdateTargetAsync(Guid ruleId, int limits);

6
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs

@ -34,12 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
case DeleteRule deleteRule:
await usageTrackerGrain.RemoveTargetAsync(deleteRule.RuleId);
break;
case EnableRule enableRule:
await usageTrackerGrain.ActivateTargetAsync(enableRule.RuleId);
break;
case DisableRule disableRule:
await usageTrackerGrain.DeactivateTargetAsync(disableRule.RuleId);
break;
case CreateRule createRule:
{
if (createRule.Trigger is UsageTrigger createdTrigger)

36
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs

@ -9,16 +9,19 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
using Orleans.Concurrency;
using Orleans.Runtime;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
[Reentrant]
public sealed class UsageTrackerGrain : GrainOfString<UsageTrackerGrain.GrainState>, IRemindable, IUsageTrackerGrain
{
private readonly IUsageTracker usageTracker;
@ -27,9 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
public int Limits { get; set; }
public bool Disabled { get; set; }
public DateTime Triggered { get; set; }
public DateTime? Triggered { get; set; }
public NamedId<Guid> AppId { get; set; }
}
@ -53,11 +54,22 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
DelayDeactivation(TimeSpan.FromDays(1));
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
RegisterTimer(x => CheckUsagesAsync(), null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
return Task.CompletedTask;
}
public async Task ReceiveReminder(string reminderName, TickStatus status)
public Task ActivateAsync()
{
return TaskHelper.Done;
}
public Task ReceiveReminder(string reminderName, TickStatus status)
{
return TaskHelper.Done;
}
public async Task CheckUsagesAsync()
{
var today = DateTime.Today;
@ -65,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
var appId = kvp.Value.AppId;
if (!IsSameMonth(today, kvp.Value.Triggered))
if (!kvp.Value.Triggered.HasValue || !IsSameMonth(today, kvp.Value.Triggered.Value))
{
var usage = await usageTracker.GetMonthlyCallsAsync(appId.Id.ToString(), today);
@ -110,20 +122,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
return WriteStateAsync();
}
public Task ActivateTargetAsync(Guid ruleId)
{
UpdateTarget(ruleId, t => t.Disabled = false);
return WriteStateAsync();
}
public Task DeactivateTargetAsync(Guid ruleId)
{
UpdateTarget(ruleId, t => t.Disabled = true);
return WriteStateAsync();
}
public Task AddTargetAsync(Guid ruleId, int limits)
{
UpdateTarget(ruleId, t => t.Limits = limits);

2
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs

@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
public sealed class UsageTriggerHandler : RuleTriggerHandler<UsageTrigger, AppUsageExceeded, EnrichedUsageExceededEvent>
{
private const string EventName = "Usage exceeeded";
private const string EventName = "Usage exceeded";
protected override Task<EnrichedUsageExceededEvent> CreateEnrichedEventAsync(Envelope<AppUsageExceeded> @event)
{

2
src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs

@ -33,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters
public RuleTriggerDto Visit(UsageTrigger trigger)
{
return SimpleMapper.Map(trigger, new UsageTriggerDto());
return SimpleMapper.Map(trigger, new UsageRuleTriggerDto());
}
public RuleTriggerDto Visit(ContentChangedTriggerV2 trigger)

2
src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageTriggerDto.cs → src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageRuleTriggerDto.cs

@ -11,7 +11,7 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers
{
public sealed class UsageTriggerDto : RuleTriggerDto
public sealed class UsageRuleTriggerDto : RuleTriggerDto
{
/// <summary>
/// The number of monthly api calls.

4
src/Squidex/Config/Domain/EntitiesServices.cs

@ -35,6 +35,7 @@ using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.Indexes;
using Squidex.Domain.Apps.Entities.Rules.UsageTracking;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Domain.Apps.Entities.Schemas.Indexes;
@ -207,6 +208,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<CreateProfileCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<UsageTrackerCommandMiddleware>()
.As<ICommandMiddleware>();
}
private static void AddBackupHandlers(this IServiceCollection services)

2
src/Squidex/Config/Orleans/SiloWrapper.cs

@ -18,6 +18,7 @@ using Orleans.Configuration;
using Orleans.Hosting;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.UsageTracking;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Log;
@ -68,6 +69,7 @@ namespace Squidex.Config.Orleans
.AddStartupTask<Bootstrap<IContentSchedulerGrain>>()
.AddStartupTask<Bootstrap<IEventConsumerManagerGrain>>()
.AddStartupTask<Bootstrap<IRuleDequeuerGrain>>()
.AddStartupTask<Bootstrap<IUsageTrackerGrain>>()
.Configure<ClusterOptions>(options =>
{
options.Configure();

1
src/Squidex/app/features/rules/declarations.ts

@ -19,6 +19,7 @@ export * from './pages/rules/actions/webhook-action.component';
export * from './pages/rules/triggers/asset-changed-trigger.component';
export * from './pages/rules/triggers/content-changed-trigger.component';
export * from './pages/rules/triggers/usage-trigger.component';
export * from './pages/rules/rule-element.component';
export * from './pages/rules/rule-wizard.component';

2
src/Squidex/app/features/rules/module.ts

@ -32,6 +32,7 @@ import {
RuleWizardComponent,
SlackActionComponent,
TweetActionComponent,
UsageTriggerComponent,
WebhookActionComponent
} from './declarations';
@ -79,6 +80,7 @@ const routes: Routes = [
RuleWizardComponent,
SlackActionComponent,
TweetActionComponent,
UsageTriggerComponent,
WebhookActionComponent
]
})

7
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -49,6 +49,13 @@
[triggerFormSubmitted]="triggerForm.submitted | async">
</sqx-content-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'Usage'">
<sqx-usage-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
</sqx-usage-trigger>
</ng-container>
</ng-container>
</form>
</ng-container>

13
src/Squidex/app/features/rules/pages/rules/triggers/usage-trigger.component.html

@ -0,0 +1,13 @@
<div [formGroup]="triggerForm" class="form-horizontal">
<div class="form-group">
<label for="condition">Limit</label>
<sqx-control-errors for="condition" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<input type="number" class="form-control" id="limit" formControlName="limit" />
<small class="form-text text-muted">
The monthly api calls to trigger.
</small>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/triggers/usage-trigger.component.scss

@ -0,0 +1,6 @@
@import '_vars';
@import '_mixins';
textarea {
height: 100px;
}

30
src/Squidex/app/features/rules/pages/rules/triggers/usage-trigger.component.ts

@ -0,0 +1,30 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'sqx-usage-trigger',
styleUrls: ['./usage-trigger.component.scss'],
templateUrl: './usage-trigger.component.html'
})
export class UsageTriggerComponent implements OnInit {
@Input()
public trigger: any;
@Input()
public triggerForm: FormGroup;
@Input()
public triggerFormSubmitted = false;
public ngOnInit() {
this.triggerForm.setControl('limit',
new FormControl(this.trigger.limit || 20000));
}
}

8
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerTests.cs → tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs

@ -25,21 +25,21 @@ using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class AssetChangedTriggerTests
public class AssetChangedTriggerHandlerTests
{
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
private readonly IRuleTriggerHandler sut;
public AssetChangedTriggerTests()
public AssetChangedTriggerHandlerTests()
{
sut = new AssetChangedTriggerHandler(scriptEngine, grainFactory);
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, "true"))
.Returns(true);
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, "false"))
.Returns(false);
sut = new AssetChangedTriggerHandler(scriptEngine, grainFactory);
}
public static IEnumerable<object[]> TestEvents = new[]

8
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerTests.cs → tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs

@ -29,7 +29,7 @@ using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentChangedTriggerTests
public class ContentChangedTriggerHandlerTests
{
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
@ -38,15 +38,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
private static readonly NamedId<Guid> SchemaMatch = NamedId.Of(Guid.NewGuid(), "my-schema1");
private static readonly NamedId<Guid> SchemaNonMatch = NamedId.Of(Guid.NewGuid(), "my-schema2");
public ContentChangedTriggerTests()
public ContentChangedTriggerHandlerTests()
{
sut = new ContentChangedTriggerHandler(scriptEngine, grainFactory);
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, "true"))
.Returns(true);
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, "false"))
.Returns(false);
sut = new ContentChangedTriggerHandler(scriptEngine, grainFactory);
}
public static IEnumerable<object[]> TestEvents = new[]
Loading…
Cancel
Save