From 4a0f23560dd82a47c4c6f37a6c0bd1fd5b460ee0 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 17:25:18 +1000 Subject: [PATCH] feat: raise event on encode outputs and subscribe via encode dialog --- .../interfaces/dashboard/EncodeDialog.tsx | 21 ++++++++++++++-- .../tables/encoding/EncodingTable.tsx | 10 ++++++-- app/src/components/ui/ScrollAnchor.tsx | 18 +++++++++++++ .../Events/RaiseEncodeOutputtedTopic.cs | 25 +++++++++++++++++++ .../Encodes/Events/EncodeOutputtedEvent.cs | 12 +++++++++ .../src/Domain/Aggregates/Encodes/Encode.cs | 7 ++++++ .../EncodeOutputtedSubscription.cs | 23 +++++++++++++++++ 7 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 app/src/components/ui/ScrollAnchor.tsx create mode 100644 src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs create mode 100644 src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs create mode 100644 src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs diff --git a/app/src/components/interfaces/dashboard/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/EncodeDialog.tsx index 5ccac359..8d40a65e 100644 --- a/app/src/components/interfaces/dashboard/EncodeDialog.tsx +++ b/app/src/components/interfaces/dashboard/EncodeDialog.tsx @@ -2,10 +2,12 @@ import type { EncodeDialogFragment$key } from '@/__generated__/EncodeDialogFragm import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' import { IconX } from '@tabler/icons-react' -import { graphql, useFragment } from 'react-relay' +import React from 'react' +import { graphql, useFragment, useSubscription } from 'react-relay' import CodeBlock from '@/components/ui/code-block/CodeBlock' import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' +import ScrollAnchor from '@/components/ui/ScrollAnchor' const FRAGMENT = graphql` fragment EncodeDialogFragment on Encode { @@ -23,6 +25,14 @@ const FRAGMENT = graphql` } ` +const OUTPUTTED_SUBSCRIPTION = graphql` + subscription EncodeDialogOutputtedSubscription { + encode_outputted { + output + } + } +` + type EncodeDialogProps = React.PropsWithChildren & { $key: EncodeDialogFragment$key } @@ -30,6 +40,11 @@ type EncodeDialogProps = React.PropsWithChildren & { const EncodeDialog: React.FC = ({ children, $key }) => { const data = useFragment(FRAGMENT, $key) + useSubscription({ + subscription: OUTPUTTED_SUBSCRIPTION, + variables: {}, + }) + return ( {children} @@ -72,7 +87,9 @@ const EncodeDialog: React.FC = ({ children, $key }) => { Logs - {data.output} + + {data.output} + )} diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index ad4c3f66..e181fe13 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -5,11 +5,12 @@ import type { } from '@/__generated__/EncodingTableFragment.graphql' import type { EncodingTableRefetchQuery } from '@/__generated__/EncodingTableRefetchQuery.graphql' -import { Button, Table, Typography } from '@giantnodes/react' +import { Button, Link, Table } from '@giantnodes/react' import { IconProgressX } from '@tabler/icons-react' import React from 'react' import { graphql, useMutation, usePaginationFragment, useSubscription } from 'react-relay' +import EncodeDialog from '@/components/interfaces/dashboard/EncodeDialog' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` @@ -32,6 +33,7 @@ const FRAGMENT = graphql` } } ...EncodeBadgesFragment + ...EncodeDialogFragment } } pageInfo { @@ -130,7 +132,11 @@ const EncodingTable: React.FC = ({ $key }) => { {(item) => ( - {item.node.file.path_info.name} + + +
diff --git a/app/src/components/ui/ScrollAnchor.tsx b/app/src/components/ui/ScrollAnchor.tsx new file mode 100644 index 00000000..972c08ea --- /dev/null +++ b/app/src/components/ui/ScrollAnchor.tsx @@ -0,0 +1,18 @@ +import React from 'react' + +type ScrollAnchorProps = React.PropsWithChildren + +const ScrollAnchor: React.FC = ({ children }) => { + const ref = React.useRef(null) + + React.useEffect(() => ref.current?.scrollIntoView({ behavior: 'smooth' })) + + return ( + <> + {children} +
+ + ) +} + +export default ScrollAnchor diff --git a/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs b/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs new file mode 100644 index 00000000..f9a51fe2 --- /dev/null +++ b/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs @@ -0,0 +1,25 @@ +using Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes.Repositories; +using HotChocolate.Subscriptions; +using MassTransit; + +namespace Giantnodes.Service.Dashboard.Application.Components.Encodes.Events; + +public class RaiseEncodeOutputtedTopic : IConsumer +{ + private readonly IEncodeRepository _repository; + private readonly ITopicEventSender _sender; + + public RaiseEncodeOutputtedTopic(IEncodeRepository repository, ITopicEventSender sender) + { + _repository = repository; + _sender = sender; + } + + public async Task Consume(ConsumeContext context) + { + var encode = await _repository.SingleAsync(x => x.Id == context.Message.EncodeId); + + await _sender.SendAsync(nameof(EncodeOutputtedEvent), encode, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs b/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs new file mode 100644 index 00000000..df858923 --- /dev/null +++ b/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs @@ -0,0 +1,12 @@ +using Giantnodes.Infrastructure.Domain.Events; + +namespace Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; + +public sealed record EncodeOutputtedEvent : DomainEvent +{ + public required Guid EncodeId { get; init; } + + public required string Output { get; init; } + + public required string FullOutput { get; init; } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs index 7936e128..f9a764c8 100644 --- a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs +++ b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs @@ -224,6 +224,13 @@ public void AppendOutputLog(string output) Guard.Against.NullOrWhiteSpace(output); Output = string.Join(Environment.NewLine, Output, output); + + DomainEvents.Add(new EncodeOutputtedEvent + { + EncodeId = Id, + Output = output, + FullOutput = Output + }); } /// diff --git a/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs new file mode 100644 index 00000000..46221f30 --- /dev/null +++ b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs @@ -0,0 +1,23 @@ +using Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes; +using Giantnodes.Service.Dashboard.Persistence.DbContexts; +using Microsoft.EntityFrameworkCore; + +namespace Giantnodes.Service.Dashboard.HttpApi.Resolvers.Encodes.Subscriptions; + +[ExtendObjectType(OperationTypeNames.Subscription)] +public class EncodeOutputtedSubscription +{ + [Subscribe] + [Topic(nameof(EncodeOutputtedEvent))] + [UseSingleOrDefault] + [UseProjection] + [UseFiltering] + [UseSorting] + public IQueryable EncodeOutputted( + [Service] ApplicationDbContext database, + [EventMessage] Encode encode) + { + return database.Encodes.Where(x => x.Id == encode.Id).AsNoTracking(); + } +} \ No newline at end of file