A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/signumsoftware/framework/commit/c3e89ed1dfd53eaaf619c2823de43cb0cc2d3154 below:

use SignalR for AlertDropdown · signumsoftware/framework@c3e89ed · GitHub

File tree Expand file treeCollapse file tree 10 files changed

+411

-27

lines changed

Filter options

Expand file treeCollapse file tree 10 files changed

+411

-27

lines changed Original file line number Diff line number Diff line change

@@ -4,7 +4,7 @@

4 4

using Signum.React.Filters;

5 5

using Signum.Entities.Alerts;

6 6 7 -

namespace Signum.React.Authorization;

7 +

namespace Signum.React.Alerts;

8 8 9 9

[ValidateModelFilter]

10 10

public class AlertController : ControllerBase

Original file line number Diff line number Diff line change

@@ -1,13 +1,10 @@

1 1

import * as React from 'react'

2 -

import { isRtl } from '@framework/AppContext'

3 2

import * as Operations from '@framework/Operations'

4 -

import { getTypeInfo, symbolNiceName } from '@framework/Reflection'

5 3

import * as Finder from '@framework/Finder'

6 4

import { is, JavascriptMessage, toLite } from '@framework/Signum.Entities'

7 -

import { Toast, NavItem, Button, ButtonGroup } from 'react-bootstrap'

5 +

import { Toast, Button, ButtonGroup } from 'react-bootstrap'

8 6

import { DateTime } from 'luxon'

9 -

import { useAPI, useAPIWithReload, useDocumentEvent, useForceUpdate, useInterval, usePrevious, useThrottle, useUpdatedRef } from '@framework/Hooks';

10 -

import { LinkContainer } from '@framework/Components'

7 +

import { useAPIWithReload, useForceUpdate, useUpdatedRef } from '@framework/Hooks';

11 8

import * as AuthClient from '../Authorization/AuthClient'

12 9

import * as Navigator from '@framework/Navigator'

13 10

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

@@ -17,27 +14,33 @@ import "./AlertDropdown.css"

17 14

import { Link } from 'react-router-dom';

18 15

import { classes, Dic } from '@framework/Globals'

19 16

import MessageModal from '@framework/Modals/MessageModal'

20 -

import { EntityLink } from '@framework/Search'

17 +

import { useSignalRCallback, useSignalRConnection } from './useSignalR'

21 18 22 -

export default function AlertDropdown(props: { checkForChangesEvery?: number, keepRingingFor?: number }) {

19 +

export default function AlertDropdown(props: { keepRingingFor?: number }) {

23 20 24 21

if (!Navigator.isViewable(AlertEntity))

25 22

return null;

26 23 27 -

return <AlertDropdownImp checkForChangesEvery={props.checkForChangesEvery ?? 30 * 1000} keepRingingFor={props.keepRingingFor ?? 10 * 1000} />;

24 +

return <AlertDropdownImp keepRingingFor={props.keepRingingFor ?? 10 * 1000} />;

28 25

}

29 26 30 -

function AlertDropdownImp(props: { checkForChangesEvery: number, keepRingingFor: number }) {

27 +

function AlertDropdownImp(props: { keepRingingFor: number }) {

28 + 29 +

const conn = useSignalRConnection("~/api/alertshub", {

30 +

accessTokenFactory: () => AuthClient.getAuthToken()!,

31 +

});

32 + 33 +

useSignalRCallback(conn, "AlertsChanged", () => {

34 +

reloadCount();

35 +

}, []);

31 36 32 37

const forceUpdate = useForceUpdate();

33 38

const [isOpen, setIsOpen] = React.useState<boolean>(false);

34 39

const [ringing, setRinging] = React.useState<boolean>(false);

35 40

const ringingRef = useUpdatedRef(ringing);

36 41 37 42

const [showAlerts, setShowAlert] = React.useState<number>(5);

38 - 39 -

var ticks = useInterval(props.checkForChangesEvery, 0, n => n + 1);

40 - 43 + 41 44

const isOpenRef = useUpdatedRef(isOpen);

42 45 43 46

var [countResult, reloadCount] = useAPIWithReload<AlertsClient.NumAlerts>((signal, oldResult) => AlertsClient.API.myAlertsCount().then(res => {

@@ -63,7 +66,7 @@ function AlertDropdownImp(props: { checkForChangesEvery: number, keepRingingFor:

63 66

}

64 67 65 68

return res;

66 -

}), [ticks], { avoidReset: true });

69 +

}), [], { avoidReset: true });

67 70 68 71

React.useEffect(() => {

69 72

if (ringing) {

@@ -75,10 +78,6 @@ function AlertDropdownImp(props: { checkForChangesEvery: number, keepRingingFor:

75 78

}

76 79

}, [ringing]);

77 80 78 -

useDocumentEvent("refresh-alerts", (e: Event) => {

79 -

reloadCount();

80 -

}, []);

81 - 82 81

const [alerts, setAlerts] = React.useState<AlertEntity[] | undefined>(undefined);

83 82

const [groupBy, setGroupBy] = React.useState<AlertDropDownGroup>("ByTypeAndUser");

84 83

@@ -108,8 +107,6 @@ function AlertDropdownImp(props: { checkForChangesEvery: number, keepRingingFor:

108 107

countResult.numAlerts -= toRemove.length;

109 108

forceUpdate();

110 109 111 - 112 - 113 110

Operations.API.executeMultiple(toRemove.map(a => toLite(a)), AlertOperation.Attend)

114 111

.then(res => {

115 112 Original file line number Diff line number Diff line change

@@ -60,11 +60,14 @@ export function start(options: { routes: JSX.Element[], showAlerts?: (typeName:

60 60 61 61

var cellFormatter = new Finder.CellFormatter((cell, ctx) => {

62 62 63 +

if (cell == null)

64 +

return undefined;

65 + 63 66

var alert: Partial<AlertEntity> = {

64 67

target: ctx.row.columns[ctx.columns.indexOf(AlertEntity.token(a => a.target).toString())],

65 68

textArguments: ctx.row.columns[ctx.columns.indexOf(AlertEntity.token(a => a.entity.textArguments).toString())]

66 69

};

67 -

return formatText(cell, alert);

70 +

return formatText(cell, alert);

68 71

});

69 72 70 73

Finder.registerPropertyFormatter(PropertyRoute.tryParse(AlertEntity, "Text"), cellFormatter);

@@ -120,10 +123,13 @@ export function getTitle(titleField: string | null, type: AlertTypeSymbol | null

120 123

if (titleField)

121 124

return titleField;

122 125 123 -

if (type!.key)

126 +

if (type == null)

127 +

return " - ";

128 + 129 +

if (type.key)

124 130

return symbolNiceName(type! as Entity & ISymbol);

125 131 126 -

return type!.name;

132 +

return type.name;

127 133

}

128 134

export function formatText(text: string, alert: Partial<AlertEntity>, onNavigated?: () => void): React.ReactElement {

129 135

var nodes: (string | React.ReactElement)[] = [];

Original file line number Diff line number Diff line change

@@ -0,0 +1,98 @@

1 +

using Microsoft.AspNetCore.Http;

2 +

using Microsoft.AspNetCore.SignalR;

3 +

using Signum.Entities.Basics;

4 +

using Signum.React.Alerts;

5 +

using Signum.React.Authorization;

6 +

using System.Threading.Tasks;

7 + 8 +

namespace Signum.React.Facades;

9 + 10 +

public interface IAlertsClient

11 +

{

12 +

Task AlertsChanged();

13 +

}

14 + 15 +

public class AlertsHub : Hub<IAlertsClient>

16 +

{

17 +

public override Task OnConnectedAsync()

18 +

{

19 +

var user = GetUser(Context.GetHttpContext()!);

20 + 21 +

AlertsServer.Connections.Add(user.ToLite(), Context.ConnectionId);

22 + 23 +

return base.OnConnectedAsync();

24 +

}

25 + 26 +

public override Task OnDisconnectedAsync(Exception? exception)

27 +

{

28 +

AlertsServer.Connections.Remove(Context.ConnectionId);

29 +

return base.OnDisconnectedAsync(exception);

30 +

}

31 + 32 +

IUserEntity GetUser(HttpContext httpContext)

33 +

{

34 +

var tokenString = httpContext.Request.Query["access_token"];

35 +

if (tokenString.Count > 1)

36 +

throw new InvalidOperationException($"{tokenString.Count} values in 'access_token' query string found");

37 + 38 +

if (tokenString.Count == 0)

39 +

{

40 +

tokenString = httpContext.Request.Headers["Authorization"];

41 + 42 +

if (tokenString.Count != 1)

43 +

throw new InvalidOperationException($"{tokenString.Count} values in 'Authorization' header found");

44 +

}

45 + 46 +

var token = AuthTokenServer.DeserializeToken(tokenString.SingleEx());

47 + 48 +

return token.User;

49 +

}

50 +

}

51 + 52 +

public class ConnectionMapping<T> where T : class

53 +

{

54 +

private readonly Dictionary<T, HashSet<string>> userToConnection = new Dictionary<T, HashSet<string>>();

55 +

private readonly Dictionary<string, T> connectionToUser = new Dictionary<string, T>();

56 + 57 +

public int Count => userToConnection.Count;

58 + 59 +

public void Add(T key, string connectionId)

60 +

{

61 +

lock (this)

62 +

{

63 +

HashSet<string>? connections;

64 +

if (!userToConnection.TryGetValue(key, out connections))

65 +

{

66 +

connections = new HashSet<string>();

67 +

userToConnection.Add(key, connections);

68 +

}

69 + 70 +

connections.Add(connectionId);

71 + 72 +

connectionToUser.Add(connectionId, key);

73 +

}

74 +

}

75 + 76 +

public IEnumerable<string> GetConnections(T key) => userToConnection.TryGetC(key) ?? Enumerable.Empty<string>();

77 + 78 +

public void Remove(string connectionId)

79 +

{

80 +

lock (this)

81 +

{

82 +

var user = connectionToUser.TryGetC(connectionId);

83 +

if (user != null)

84 +

{

85 +

HashSet<string>? connections = userToConnection.TryGetC(user);

86 +

if (connections != null)

87 +

{

88 +

connections.Remove(connectionId);

89 +

if (connections.Count == 0)

90 +

userToConnection.Remove(user);

91 +

}

92 + 93 +

connectionToUser.Remove(connectionId);

94 +

}

95 +

}

96 +

}

97 +

}

98 + Original file line number Diff line number Diff line change

@@ -1,12 +1,93 @@

1 1

using Microsoft.AspNetCore.Builder;

2 +

using Microsoft.AspNetCore.Routing;

3 +

using Microsoft.AspNetCore.SignalR;

4 +

using Signum.Entities.Alerts;

5 +

using Signum.Entities.Authorization;

6 +

using Signum.Entities.Basics;

7 +

using Signum.React.Facades;

2 8 3 9

namespace Signum.React.Alerts;

4 10 5 11

public static class AlertsServer

6 12

{

13 +

internal static ConnectionMapping<Lite<IUserEntity>> Connections = null!;

14 + 15 +

public static IHubContext<AlertsHub, IAlertsClient> AlertsHub { get; private set; }

16 + 7 17

public static void Start(IApplicationBuilder app)

8 18

{

9 19

SignumControllerFactory.RegisterArea(MethodInfo.GetCurrentMethod());

20 +

}

21 + 22 +

public static void MapAlertsHub(IEndpointRouteBuilder endpoints)

23 +

{

24 +

endpoints.MapHub<AlertsHub>("/api/alertshub");

25 +

Connections = new ConnectionMapping<Lite<IUserEntity>>();

26 +

AlertsHub = (IHubContext<AlertsHub, IAlertsClient>)endpoints.ServiceProvider.GetService(typeof(IHubContext<AlertsHub, IAlertsClient>))!;

27 + 28 +

var alertEvents = Schema.Current.EntityEvents<AlertEntity>();

29 + 30 +

alertEvents.Saved += AlertEvents_Saved;

31 +

alertEvents.PreUnsafeDelete += AlertEvents_PreUnsafeDelete;

32 +

alertEvents.PreUnsafeUpdate += AlertEvents_PreUnsafeUpdate;

33 +

alertEvents.PreUnsafeInsert += AlertEvents_PreUnsafeInsert;

34 +

}

35 + 36 +

private static IDisposable? AlertEvents_PreUnsafeUpdate(IUpdateable update, IQueryable<AlertEntity> entityQuery)

37 +

{

38 +

NotifyOnCommitQuery(entityQuery);

39 +

return null;

40 +

}

41 + 42 +

private static LambdaExpression AlertEvents_PreUnsafeInsert(IQueryable query, LambdaExpression constructor, IQueryable<AlertEntity> entityQuery)

43 +

{

44 +

NotifyOnCommitQuery(entityQuery);

45 +

return constructor;

46 +

}

10 47 48 +

private static IDisposable? AlertEvents_PreUnsafeDelete(IQueryable<AlertEntity> entityQuery)

49 +

{

50 +

NotifyOnCommitQuery(entityQuery);

51 +

return null;

52 +

}

53 + 54 +

private static void AlertEvents_Saved(AlertEntity ident, SavedEventArgs args)

55 +

{

56 +

if (ident.Recipient != null)

57 +

NotifyOnCommit(ident.Recipient);

58 +

}

59 + 60 +

private static void NotifyOnCommitQuery(IQueryable<AlertEntity> alerts)

61 +

{

62 +

var recipients = alerts.Where(a => a.Recipient != null && a.State == AlertState.Saved).Select(a => a.Recipient!).Distinct().ToArray();

63 +

if (recipients.Any())

64 +

NotifyOnCommit(recipients);

65 +

}

66 +

private static void NotifyOnCommit(params Lite<IUserEntity>[] recipients)

67 +

{

68 +

var hs = (HashSet<Lite<IUserEntity>>)Transaction.UserData.GetOrCreate("AlertRecipients", new HashSet<Lite<IUserEntity>>());

69 +

hs.AddRange(recipients);

70 + 71 +

Transaction.PostRealCommit -= Transaction_PostRealCommit;

72 +

Transaction.PostRealCommit += Transaction_PostRealCommit;

73 +

}

74 + 75 +

private static void Transaction_PostRealCommit(Dictionary<string, object> dic)

76 +

{

77 +

var hashSet = (HashSet<Lite<IUserEntity>>)dic["AlertRecipients"];

78 +

foreach (var user in hashSet)

79 +

{

80 +

foreach (var connectionId in Connections.GetConnections(user))

81 +

{

82 +

try

83 +

{

84 +

AlertsServer.AlertsHub.Clients.Client(connectionId).AlertsChanged();

85 +

}

86 +

catch(Exception ex)

87 +

{

88 +

ex.LogException();

89 +

}

90 +

}

91 +

}

11 92

}

12 93

}

You can’t perform that action at this time.


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4