Skip to content

Commit

Permalink
fixes in ConcurrentUser.tsx
Browse files Browse the repository at this point in the history
  • Loading branch information
JafarMirzaie committed Mar 15, 2022
1 parent 5cf4cad commit 4031bfb
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 6 deletions.
17 changes: 11 additions & 6 deletions Signum.React.Extensions/ConcurrentUser/ConcurrentUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ConcurrentUserEntity, ConcurrentUserMessage } from './Signum.Entities.C
import { OverlayTrigger, Popover } from 'react-bootstrap';
import { Entity, Lite, liteKey, toLite } from '@framework/Signum.Entities'
import { UserEntity } from '../Authorization/Signum.Entities.Authorization'
import { useAPI, useUpdatedRef } from '../../Signum.React/Scripts/Hooks'
import { useAPI, useForceUpdate, useUpdatedRef } from '../../Signum.React/Scripts/Hooks'
import { GraphExplorer } from '@framework/Reflection'
import * as Navigator from '@framework/Navigator'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand All @@ -23,13 +23,17 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void
const userKey = liteKey(toLite(AppContext.currentUser! as UserEntity))
const startTime = React.useMemo(() => DateTime.utc().toISO(), [entityKey]);

const [ticks, setTicks] = React.useState(p.entity.ticks);
const [ticks, setTicks] = React.useState<string>(p.entity.ticks);
const forceUpdate = useForceUpdate();
React.useEffect(() => {
setTicks(p.entity.ticks);
}, [entityKey]);

useSignalRGroup(conn, {
enterGroup: co => p.entity == null ? Promise.resolve(undefined) : co.send("EnterEntity", entityKey, startTime, userKey),
exitGroup: co => p.entity == null ? Promise.resolve(undefined) : co.send("ExitEntity", entityKey, startTime, userKey),
deps: [entityKey]
});
});

const isModified = React.useRef(false);

Expand Down Expand Up @@ -57,7 +61,7 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void

var [refreshKey, setRefreshKey] = React.useState(0);

var concurrentUsers = useAPI(() => ConcurrentUserClient.API.getUsers(entityKey), [refreshKey, isModified.current]);
var concurrentUsers = useAPI(() => ConcurrentUserClient.API.getUsers(entityKey), [refreshKey, isModified.current, entityKey]);

useSignalRCallback(conn, "EntitySaved", (a: string) => setTicks(a), []);

Expand All @@ -69,7 +73,7 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void

React.useEffect(() => {
const handle = setTimeout(() => {
if (ticksRef.current != entityRef.current.ticks) {
if (ticksRef.current != null && ticksRef.current != entityRef.current.ticks) {
MessageModal.show({
title: ConcurrentUserMessage.DatabaseChangesDetected.niceToString(),
style: "warning",
Expand Down Expand Up @@ -104,7 +108,8 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void

return (
<OverlayTrigger
trigger="click"
trigger="click"
onToggle={show => forceUpdate()}
placement={"bottom-end"}
overlay={
<Popover>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ConcurrentUserEntity extends Entities.Entity {
}

export module ConcurrentUserMessage {
export const ConcurrentUsers = new MessageKey("ConcurrentUserMessage", "ConcurrentUsers");
export const CurrentlyEditing = new MessageKey("ConcurrentUserMessage", "CurrentlyEditing");
export const DatabaseChangesDetected = new MessageKey("ConcurrentUserMessage", "DatabaseChangesDetected");
export const LooksLikeSomeoneJustSaved0ToTheDatabase = new MessageKey("ConcurrentUserMessage", "LooksLikeSomeoneJustSaved0ToTheDatabase");
Expand Down

1 comment on commit 4031bfb

@olmobrutall
Copy link
Collaborator

@olmobrutall olmobrutall commented on 4031bfb Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presenting ConcurrentUser

ConcurrentUser is a new extension to prevent ConcurrencyExceptions.

It's a widget based in SignalR that appears when the two or more users are viewing/modifying the same entity at the same time.

Let's see how it works:

Without local changes

  1. When two users open the same entity the widget appears with a green icon, and on clicking a Popover shows the current status:
    image

  2. If the other user starts making local changes, the icon becomes yellow and an icon shows that the user has started modifying the entity:
    image

  3. Once the other saves the changes to the database the icon becomes red and a MessageModal appears:
    image

If you choose not to reload, you can always do it again by clicking in the Popover

image

With local changes

  1. If you have local changes the icon stats blincking intermitently and the Popover shows some extra message.
    image

  2. If the other user starts making changes too, looks like this:
    image

  3. And if the other user overtakes you and saves, then the MessageModal shows a warning and suggests to re-apply the changes over the new version of the entity in another tab (to copy-paste your changes).

image

If you dismiss the MessageModal, you can still see the suggestion in the Popover

image

How it works

There is a new ConcurrentUserEntity that is saved in the database to keep track of the current opened entities by user and SignalR ConnectionId.

When the entity is closed, the ConcurrentUserEntity is deleted. Since SignalR uses persistent connections, even closing the tab will delete the ConcurrentUserEntity, but when you stop the server (debugging, or deployment) zombie ConcurrentUserEntity could be seen.

You can manually delete them for now with the delete operation. Maybe in the future we will make a better solution (ScheduledTask / Delete on start).

SignalR limitations in IIS for Windows Clients

IIS imposes a limit of 10 simultaneous connections on Windows Client machines (Windows 10, Windows 11, etc...)

https://docs.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-3.1#iis-limitations-on-windows-client-os

In practice this means that when debugging in IIS, if you open more than 3 tabs the server becomes unresponsive untill you close one tab:

To prevent this from happening we disable SignalR on Debug mode in IIS.

image

Use IIS Express to debug this feature.

How to activate it

ConcurrentUser is already part of Southwind and checked by default for new applications.

You can activate it in your current app by applying the changes in Starter.cs, Startup.cs, MainAdmin.cs, and Index.cshtml from this commit:

signumsoftware/southwind@66e0909

Thanks @JafarMirzaie!

Please sign in to comment.