Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/state-extensions #46

Merged
merged 12 commits into from
Jul 23, 2024
25 changes: 12 additions & 13 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Cratis.Fundamentals" Version="5.3.1" />
<PackageVersion Include="Cratis.Applications.ProxyGenerator.Build" Version="1.0.0" />
<PackageVersion Include="Cratis.Fundamentals" Version="5.3.2" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.9.5" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.9.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
Expand All @@ -29,19 +28,19 @@
<PackageVersion Include="polly.core" Version="8.3.1" />
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="8.0.0" />
<!-- Orleans -->
<PackageVersion Include="Microsoft.Orleans.SDK" Version="8.1.0" />
<PackageVersion Include="Microsoft.Orleans.Runtime" Version="8.1.0" />
<PackageVersion Include="Microsoft.Orleans.Server" Version="8.1.0" />
<PackageVersion Include="Microsoft.Orleans.Serialization" Version="8.1.0" />
<PackageVersion Include="Microsoft.Orleans.Serialization.SystemTextJson" Version="8.1.0" />
<PackageVersion Include="Microsoft.Orleans.Core.Abstractions" Version="8.1.0" />
<PackageVersion Include="OrleansTestKit" Version="8.1.0" />
<PackageVersion Include="OrleansDashboard" Version="8.0.0" />
<PackageVersion Include="Microsoft.Orleans.SDK" Version="8.2.0" />
<PackageVersion Include="Microsoft.Orleans.Runtime" Version="8.2.0" />
<PackageVersion Include="Microsoft.Orleans.Server" Version="8.2.0" />
<PackageVersion Include="Microsoft.Orleans.Serialization" Version="8.2.0" />
<PackageVersion Include="Microsoft.Orleans.Serialization.SystemTextJson" Version="8.2.0" />
<PackageVersion Include="Microsoft.Orleans.Core.Abstractions" Version="8.2.0" />
<PackageVersion Include="OrleansTestKit" Version="8.2.0" />
<PackageVersion Include="OrleansDashboard" Version="8.2.0" />
<!-- Roslyn-->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.10.0" />
<!-- Analysis -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="4.10.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" NoWarn="NU5104" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.12.2" />
Expand Down
4 changes: 2 additions & 2 deletions Samples/eCommerce/Basic/Web/API/Products/AllProducts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// eslint-disable-next-line header/header
import { QueryFor, QueryResultWithState, Sorting, SortingActions, SortingActionsForQuery, Paging } from '@cratis/applications/queries';
import { useQuery, useQueryWithPaging, PerformQuery, SetSorting, SetPage } from '@cratis/applications.react/queries';
import { useQuery, useQueryWithPaging, PerformQuery, SetSorting, SetPage, SetPageSize } from '@cratis/applications.react/queries';
import { Product } from './Product';
import Handlebars from 'handlebars';

Expand Down Expand Up @@ -77,7 +77,7 @@ export class AllProducts extends QueryFor<Product[]> {
return useQuery<Product[], AllProducts>(AllProducts, undefined, sorting);
}

static useWithPaging(pageSize: number, sorting?: Sorting): [QueryResultWithState<Product[]>, number, PerformQuery, SetSorting, SetPage] {
static useWithPaging(pageSize: number, sorting?: Sorting): [QueryResultWithState<Product[]>, number, PerformQuery, SetSorting, SetPage, SetPageSize] {
return useQueryWithPaging<Product[], AllProducts>(AllProducts, new Paging(0, pageSize), undefined, sorting);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// eslint-disable-next-line header/header
import { ObservableQueryFor, QueryResultWithState, Sorting, SortingActions, SortingActionsForObservableQuery, Paging } from '@cratis/applications/queries';
import { useObservableQuery, useObservableQueryWithPaging, SetSorting, SetPage } from '@cratis/applications.react/queries';
import { useObservableQuery, useObservableQueryWithPaging, SetSorting, SetPage, SetPageSize } from '@cratis/applications.react/queries';
import { Product } from './Product';
import Handlebars from 'handlebars';

Expand Down Expand Up @@ -77,7 +77,7 @@ export class ObserveAllProducts extends ObservableQueryFor<Product[]> {
return useObservableQuery<Product[], ObserveAllProducts>(ObserveAllProducts, undefined, sorting);
}

static useWithPaging(pageSize: number, sorting?: Sorting): [QueryResultWithState<Product[]>, SetSorting, SetPage] {
static useWithPaging(pageSize: number, sorting?: Sorting): [QueryResultWithState<Product[]>, SetSorting, SetPage, SetPageSize] {
return useObservableQueryWithPaging<Product[], ObserveAllProducts>(ObserveAllProducts, new Paging(0, pageSize), undefined, sorting);
}
}
16 changes: 11 additions & 5 deletions Samples/eCommerce/Basic/Web/Catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,41 @@ import { useEffect, useState } from 'react';
import { SortDirection, Sorting } from '@cratis/applications/queries';

export const Catalog = withViewModel(CatalogViewModel, ({ viewModel }) => {
const [pageSize, setPageSize] = useState(10);
// // const [products, currentPage, perform, setSorting, setPage] = AllProducts.useWithPaging(pageSize);
const [observableProducts, setSorting, setPage] = ObserveAllProducts.useWithPaging(10);
// // const [products, perform, setSorting, setPage] = AllProducts.useWithPaging(pageSize);
const [observableProducts, setSorting, setPage, setPageSize] = ObserveAllProducts.useWithPaging(10);
const [descending, setDescending] = useState(false);
const [currentPage, setCurrentPage] = useState(0);

return (
<div>
<div>Page {currentPage + 1}</div>
<div>Page {currentPage + 1} of {observableProducts.paging.totalPages}</div>
<DataTable value={observableProducts.data}>
<Column field="id" header="SKU" />
<Column field="name" header="Name" />
</DataTable>
Total items: {observableProducts.paging.totalItems}
<br />

<button onClick={() => {
setCurrentPage(currentPage - 1);
setPage(currentPage - 1);
}}>Previous page</button>

&nbsp;
&nbsp;

<button onClick={() => {
setCurrentPage(currentPage + 1);
setPage(currentPage + 1);
}}>Next page</button>
<br />

<button onClick={() => {
setPage(20);
setPageSize(20);
}}>More stuff</button>

&nbsp;

<button onClick={() => {
if (descending) {
setSorting(AllProducts.sortBy.id.ascending);
Expand Down
11 changes: 10 additions & 1 deletion Source/DotNET/Applications/Queries/ClientObservable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ namespace Cratis.Applications.Queries;
/// <remarks>
/// Initializes a new instance of the <see cref="ClientObservable{T}"/> class.
/// </remarks>
/// <param name="queryContext">The <see cref="QueryContext"/> the observable is for.</param>
/// <param name="subject">The <see cref="ISubject{T}"/> the observable wraps.</param>
/// <param name="jsonOptions">The <see cref="JsonOptions"/>.</param>
public class ClientObservable<T>(ISubject<T> subject, JsonOptions jsonOptions) : IClientObservable, IAsyncEnumerable<T>
public class ClientObservable<T>(
QueryContext queryContext,
ISubject<T> subject,
JsonOptions jsonOptions) : IClientObservable, IAsyncEnumerable<T>
{
/// <summary>
/// Notifies all subscribed and future observers about the arrival of the specified element in the sequence.
Expand All @@ -37,6 +41,11 @@ public async Task HandleConnection(ActionExecutingContext context)
#pragma warning disable MA0147 // Avoid async void method for delegate
subscription = subject.Subscribe(async _ =>
{
queryResult.Paging = new(
queryContext.Paging.Page,
queryContext.Paging.Size,
queryContext.TotalItems);

queryResult.Data = _!;

try
Expand Down
21 changes: 13 additions & 8 deletions Source/DotNET/Applications/Queries/Paging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,7 @@ public Paging(int page, int size, bool isPaged)
{
if (isPaged)
{
if (page < 0)
{
throw new ArgumentOutOfRangeException(nameof(page), "Page number must be greater or equal to 0");
}
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), "Page size must be greater than 0");
}
Validate(page, size);
}
Page = page;
Size = size;
Expand All @@ -56,4 +49,16 @@ public Paging(int page, int size, bool isPaged)
/// Gets the number of items to skip.
/// </summary>
public int Skip => Page * Size;

void Validate(int page, int size)
{
if (page < 0)
{
throw new ArgumentOutOfRangeException(nameof(page), "Page number must be greater or equal to 0");
}
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), "Page size must be greater than 0");
}
}
}
10 changes: 7 additions & 3 deletions Source/DotNET/Applications/Queries/PagingInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ namespace Cratis.Applications.Queries;
/// <param name="Page">The page number.</param>
/// <param name="Size">The size of the page.</param>
/// <param name="TotalItems">The total number of items.</param>
/// <param name="TotalPages">The total number of pages.</param>
public record PagingInfo(int Page, int Size, int TotalItems, int TotalPages)
public record PagingInfo(int Page, int Size, long TotalItems)
{
/// <summary>
/// Represents a not paged result.
/// </summary>
public static readonly PagingInfo NotPaged = new(0, 0, 0, 0);
public static readonly PagingInfo NotPaged = new(0, 0, 0);

/// <summary>
/// Gets the total number of pages.
/// </summary>
public int TotalPages => Size == 0 ? 0 : (int)Math.Ceiling((double)TotalItems / Size);
}
5 changes: 2 additions & 3 deletions Source/DotNET/Applications/Queries/QueryActionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
Paging = queryContext.Paging == Paging.NotPaged ? PagingInfo.NotPaged : new PagingInfo(
queryContext.Paging.Page,
queryContext.Paging.Size,
response.TotalItems,
response.TotalItems / queryContext.Paging.Size),
response.TotalItems),
CorrelationId = context.HttpContext.GetCorrelationId(),
ValidationResults = context.ModelState.SelectMany(_ => _.Value!.Errors.Select(p => p.ToValidationResult(_.Key.ToCamelCase()))),
ExceptionMessages = callResult.ExceptionMessages,
Expand Down Expand Up @@ -188,7 +187,7 @@ IClientObservable CreateClientObservableFrom(ObjectResult objectResult)
var type = objectResult.Value!.GetType();
var subjectType = type.GetInterfaces().First(_ => _.IsGenericType && _.GetGenericTypeDefinition() == typeof(ISubject<>));
var clientObservableType = typeof(ClientObservable<>).MakeGenericType(subjectType.GetGenericArguments()[0]);
return (Activator.CreateInstance(clientObservableType, objectResult.Value, _options) as IClientObservable)!;
return (Activator.CreateInstance(clientObservableType, queryContextManager.Current, objectResult.Value, _options) as IClientObservable)!;
}

bool IsStreamingResult(ObjectResult objectResult) => IsAsyncEnumerableResult(objectResult) || IsSubjectResult(objectResult);
Expand Down
8 changes: 7 additions & 1 deletion Source/DotNET/Applications/Queries/QueryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ namespace Cratis.Applications.Queries;
/// <param name="CorrelationId">The <see cref="CorrelationId"/> for the query.</param>
/// <param name="Paging">The <see cref="Paging"/> information.</param>
/// <param name="Sorting">The <see cref="Sorting"/> information.</param>
public record QueryContext(CorrelationId CorrelationId, Paging Paging, Sorting Sorting);
public record QueryContext(CorrelationId CorrelationId, Paging Paging, Sorting Sorting)
{
/// <summary>
/// Gets the total number of items in the query.
/// </summary>
public long TotalItems { get; set; }
}
49 changes: 32 additions & 17 deletions Source/DotNET/MongoDB/MongoCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,10 @@ static ISubject<TResult> Observe<TDocument, TResult>(
var documents = new List<TDocument>();
var subject = createSubject([]);

var logger = Internals.ServiceProvider.GetService<ILogger<MongoCollection>>()!;
var queryContext = Internals.ServiceProvider.GetService<IQueryContextManager>()!.Current;
var invalidateFindOnAdd = queryContext.Paging.IsPaged || queryContext.Sorting != Sorting.None;
var logger = Internals.ServiceProvider.GetRequiredService<ILogger<MongoCollection>>();
var queryContextManager = Internals.ServiceProvider.GetRequiredService<IQueryContextManager>();
var queryContext = Internals.ServiceProvider.GetRequiredService<IQueryContextManager>().Current;
var invalidateFindOnAddOrDelete = queryContext.Paging.IsPaged || queryContext.Sorting != Sorting.None;

var options = new ChangeStreamOptions
{
Expand Down Expand Up @@ -207,6 +208,8 @@ async Task Watch()
try
{
var response = findCall();
await UpdateTotalItems(queryContext, response);

response = AddSorting(queryContext, response);
response = AddPaging(queryContext, response);
documents = response.ToList();
Expand All @@ -216,7 +219,7 @@ async Task Watch()
subject.Subscribe(_ => { }, cursor.Dispose);

await cursor.ForEachAsync(
changeDocument =>
async changeDocument =>
{
if (subject is Subject<TResult> disposableSubject && disposableSubject.IsDisposed &&
subject is BehaviorSubject<TResult> disposableBehaviorSubject && disposableBehaviorSubject.IsDisposed)
Expand All @@ -226,7 +229,15 @@ await cursor.ForEachAsync(
return;
}

documents = HandleChange(onNext, changeDocument, invalidateFindOnAdd, response, documents, subject, idProperty);
documents = await HandleChange(
queryContext,
onNext,
changeDocument,
invalidateFindOnAddOrDelete,
response,
documents,
subject,
idProperty);
},
cancellationToken);
}
Expand All @@ -251,10 +262,16 @@ await cursor.ForEachAsync(
return subject;
}

static List<TDocument> HandleChange<TDocument, TResult>(
static async Task UpdateTotalItems<TDocument>(QueryContext queryContext, IFindFluent<TDocument, TDocument> response)
{
queryContext.TotalItems = await response.CountDocumentsAsync();
}

static async Task<List<TDocument>> HandleChange<TDocument, TResult>(
QueryContext queryContext,
Action<IEnumerable<TDocument>, ISubject<TResult>> onNext,
ChangeStreamDocument<TDocument> changeDocument,
bool invalidateFindOnAdd,
bool invalidateFindOnAddOrDelete,
IFindFluent<TDocument, TDocument> response,
List<TDocument> documents,
ISubject<TResult> subject,
Expand All @@ -267,11 +284,7 @@ static List<TDocument> HandleChange<TDocument, TResult>(
var document = documents.Find(_ => idProperty.GetValue(_)!.Equals(id));
if (changeDocument.OperationType == ChangeStreamOperationType.Delete && document is not null)
{
if (invalidateFindOnAdd)
{
documents = response.ToList();
}
else
if (!invalidateFindOnAddOrDelete)
{
documents.Remove(document);
}
Expand All @@ -285,16 +298,18 @@ static List<TDocument> HandleChange<TDocument, TResult>(
}
else if (changeDocument.OperationType == ChangeStreamOperationType.Insert)
{
if (invalidateFindOnAdd)
{
documents = response.ToList();
}
else
if (!invalidateFindOnAddOrDelete)
{
documents.Add(changeDocument.FullDocument);
}
hasChanges = true;
}

if (invalidateFindOnAddOrDelete)
{
await UpdateTotalItems(queryContext, response);
documents = await response.ToListAsync();
}
}

if (hasChanges)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IIntercep
{
if (method.Name == nameof(IMongoClient.GetDatabase))
{
return new[] { new MongoClientInterceptor(proxyGenerator, resiliencePipeline, mongoClient) };
return [new MongoClientInterceptor(proxyGenerator, resiliencePipeline, mongoClient)];
}
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void Intercept(IInvocation invocation)
catch (Exception ex)
{
openConnectionSemaphore.Release(1);
setExceptionMethod.Invoke(tcs, new[] { ex });
setExceptionMethod.Invoke(tcs, [ex]);
}

return ValueTask.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IIntercep
{
if (method.ReturnType.IsGenericType)
{
return new[] { new MongoCollectionInterceptorForReturnValues(resiliencePipeline, semaphore) };
return [new MongoCollectionInterceptorForReturnValues(resiliencePipeline, semaphore)];
}

return new[] { new MongoCollectionInterceptor(resiliencePipeline, semaphore) };
return [new MongoCollectionInterceptor(resiliencePipeline, semaphore)];
}
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IIntercep
{
if (method.Name == nameof(IMongoDatabase.GetCollection))
{
return new[] { new MongoDatabaseInterceptor(proxyGenerator, resiliencePipeline, mongoClient) };
return [new MongoDatabaseInterceptor(proxyGenerator, resiliencePipeline, mongoClient)];
}
return [];
}
Expand Down
1 change: 0 additions & 1 deletion Source/DotNET/Orleans.MongoDB/MongoDBGrainStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Cratis.Applications.MongoDB;
using Cratis.Concepts;
using MongoDB.Driver;
using Orleans.Runtime;
using Orleans.Storage;

namespace Cratis.Applications.Orleans.MongoDB;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class with_unknown_current_state : given.a_state_machine
{
Exception exception;

protected override IEnumerable<IState<StateMachineStateForTesting>> CreateStates() => new[] { new StateThatSupportsTransitioningFrom() };
protected override IEnumerable<IState<StateMachineStateForTesting>> CreateStates() => [new StateThatSupportsTransitioningFrom()];

void Establish()
{
Expand Down
Loading
Loading