Merge remote-tracking branch 'upstream/v3.0.x-release' into pres-graphql

This commit is contained in:
Ramón Souza 2023-10-04 08:04:35 -03:00
commit 128e89fc83
22 changed files with 217 additions and 87 deletions

View File

@ -18,7 +18,8 @@ case class MeetingLockSettingsDbModel(
hideUserList: Boolean,
lockOnJoin: Boolean,
lockOnJoinConfigurable: Boolean,
hideViewersCursor: Boolean
hideViewersCursor: Boolean,
hideViewersAnnotation: Boolean
)
class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsDbModel](tag, "meeting_lockSettings") {
@ -32,10 +33,11 @@ class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsD
val lockOnJoin = column[Boolean]("lockOnJoin")
val lockOnJoinConfigurable = column[Boolean]("lockOnJoinConfigurable")
val hideViewersCursor = column[Boolean]("hideViewersCursor")
val hideViewersAnnotation = column[Boolean]("hideViewersAnnotation")
// def fk_meetingId: ForeignKeyQuery[MeetingDbTableDef, MeetingDbModel] = foreignKey("fk_meetingId", meetingId, TableQuery[MeetingDbTableDef])(_.meetingId)
override def * : ProvenShape[MeetingLockSettingsDbModel] = (meetingId, disableCam, disableMic, disablePrivateChat, disablePublicChat, disableNotes, hideUserList, lockOnJoin, lockOnJoinConfigurable, hideViewersCursor) <> (MeetingLockSettingsDbModel.tupled, MeetingLockSettingsDbModel.unapply)
override def * : ProvenShape[MeetingLockSettingsDbModel] = (meetingId, disableCam, disableMic, disablePrivateChat, disablePublicChat, disableNotes, hideUserList, lockOnJoin, lockOnJoinConfigurable, hideViewersCursor, hideViewersAnnotation) <> (MeetingLockSettingsDbModel.tupled, MeetingLockSettingsDbModel.unapply)
}
object MeetingLockSettingsDAO {
@ -52,7 +54,8 @@ object MeetingLockSettingsDAO {
hideUserList = lockSettingsProps.hideUserList,
lockOnJoin = lockSettingsProps.lockOnJoin,
lockOnJoinConfigurable = lockSettingsProps.lockOnJoinConfigurable,
hideViewersCursor = lockSettingsProps.hideViewersCursor
hideViewersCursor = lockSettingsProps.hideViewersCursor,
hideViewersAnnotation = lockSettingsProps.hideViewersAnnotation,
)
)
).onComplete {
@ -76,7 +79,8 @@ object MeetingLockSettingsDAO {
hideUserList = permissions.hideUserList,
lockOnJoin = permissions.lockOnJoin,
lockOnJoinConfigurable = permissions.lockOnJoinConfigurable,
hideViewersCursor = permissions.hideViewersCursor
hideViewersCursor = permissions.hideViewersCursor,
hideViewersAnnotation = permissions.hideViewersAnnotation,
),
)
).onComplete {

View File

@ -3,7 +3,6 @@ package main
import (
"fmt"
"github.com/iMDT/bbb-graphql-middleware/internal/msgpatch"
"github.com/iMDT/bbb-graphql-middleware/internal/rediscli"
"github.com/iMDT/bbb-graphql-middleware/internal/websrv"
log "github.com/sirupsen/logrus"
"net/http"
@ -21,7 +20,7 @@ func main() {
msgpatch.ClearAllCaches()
// Listen msgs from akka (for example to invalidate connection)
go rediscli.StartRedisListener()
go websrv.StartRedisListener()
// Websocket listener
// set default port

View File

@ -1,13 +0,0 @@
package rediscli
import "github.com/redis/go-redis/v9"
var redisClient = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
func GetRedisConn() *redis.Client {
return redisClient
}

View File

@ -1,49 +0,0 @@
package rediscli
import (
"context"
"encoding/json"
log "github.com/sirupsen/logrus"
"strings"
)
func StartRedisListener() {
log := log.WithField("_routine", "StartRedisListener")
var ctx = context.Background()
subscriber := GetRedisConn().Subscribe(ctx, "from-akka-apps-redis-channel")
for {
msg, err := subscriber.ReceiveMessage(ctx)
if err != nil {
log.Errorf("error: ", err)
}
// Skip parsing unnecessary messages
if !strings.Contains(msg.Payload, "InvalidateUserGraphqlConnectionSysMsg") {
continue
}
var message interface{}
if err := json.Unmarshal([]byte(msg.Payload), &message); err != nil {
panic(err)
}
messageAsMap := message.(map[string]interface{})
messageEnvelopeAsMap := messageAsMap["envelope"].(map[string]interface{})
messageType := messageEnvelopeAsMap["name"]
if messageType == "InvalidateUserGraphqlConnectionSysMsg" {
messageCoreAsMap := messageAsMap["core"].(map[string]interface{})
messageBodyAsMap := messageCoreAsMap["body"].(map[string]interface{})
sessionTokenToInvalidate := messageBodyAsMap["sessionToken"]
log.Debugf("Received invalidate request for sessionToken %v", sessionTokenToInvalidate)
//Not being used yet
//websrv.InvalidateSessionTokenConnections(sessionTokenToInvalidate.(string))
}
}
}

View File

@ -6,7 +6,6 @@ import (
"github.com/iMDT/bbb-graphql-middleware/internal/common"
"github.com/iMDT/bbb-graphql-middleware/internal/hascli"
"github.com/iMDT/bbb-graphql-middleware/internal/msgpatch"
"github.com/iMDT/bbb-graphql-middleware/internal/rediscli"
"github.com/iMDT/bbb-graphql-middleware/internal/websrv/reader"
"github.com/iMDT/bbb-graphql-middleware/internal/websrv/writer"
log "github.com/sirupsen/logrus"
@ -65,7 +64,7 @@ func ConnectionHandler(w http.ResponseWriter, r *http.Request) {
sessionTokenRemoved := BrowserConnections[browserConnectionId].SessionToken
delete(BrowserConnections, browserConnectionId)
BrowserConnectionsMutex.Unlock()
go rediscli.SendUserGraphqlConnectionClosedSysMsg(sessionTokenRemoved, browserConnectionId)
go SendUserGraphqlConnectionClosedSysMsg(sessionTokenRemoved, browserConnectionId)
log.Infof("connection removed")
}()
@ -136,7 +135,7 @@ func InvalidateSessionTokenConnections(sessionTokenToInvalidate string) {
browserConnection.HasuraConnection.ContextCancelFunc()
log.Debugf("Processed invalidate request for sessionToken %v (hasura connection %v)", sessionTokenToInvalidate, browserConnection.HasuraConnection.Id)
//go SendInvalidatedUserGraphqlConnectionEvtMsg(browserConnection.SessionToken)
go SendUserGraphqlConnectionInvalidatedEvtMsg(browserConnection.SessionToken)
}
}
}

View File

@ -2,7 +2,6 @@ package websrv
import (
"context"
"github.com/iMDT/bbb-graphql-middleware/internal/rediscli"
log "github.com/sirupsen/logrus"
"sync"
)
@ -36,7 +35,7 @@ func ConnectionInitHandler(browserConnectionId string, browserConnectionContext
browserConnection.SessionToken = sessionToken
BrowserConnectionsMutex.Unlock()
go rediscli.SendUserGraphqlConnectionStablishedSysMsg(sessionToken, browserConnectionId)
go SendUserGraphqlConnectionStablishedSysMsg(sessionToken, browserConnectionId)
break
}

View File

@ -1,12 +1,66 @@
package rediscli
package websrv
import (
"context"
"encoding/json"
"fmt"
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"strings"
"time"
)
var redisClient = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
func GetRedisConn() *redis.Client {
return redisClient
}
func StartRedisListener() {
log := log.WithField("_routine", "StartRedisListener")
var ctx = context.Background()
subscriber := GetRedisConn().Subscribe(ctx, "from-akka-apps-redis-channel")
for {
msg, err := subscriber.ReceiveMessage(ctx)
if err != nil {
log.Errorf("error: ", err)
}
// Skip parsing unnecessary messages
if !strings.Contains(msg.Payload, "InvalidateUserGraphqlConnectionSysMsg") {
continue
}
var message interface{}
if err := json.Unmarshal([]byte(msg.Payload), &message); err != nil {
panic(err)
}
messageAsMap := message.(map[string]interface{})
messageEnvelopeAsMap := messageAsMap["envelope"].(map[string]interface{})
messageType := messageEnvelopeAsMap["name"]
if messageType == "InvalidateUserGraphqlConnectionSysMsg" {
messageCoreAsMap := messageAsMap["core"].(map[string]interface{})
messageBodyAsMap := messageCoreAsMap["body"].(map[string]interface{})
sessionTokenToInvalidate := messageBodyAsMap["sessionToken"]
log.Debugf("Received invalidate request for sessionToken %v", sessionTokenToInvalidate)
//Not being used yet
InvalidateSessionTokenConnections(sessionTokenToInvalidate.(string))
}
}
}
func getCurrTimeInMs() int64 {
currentTime := time.Now()
milliseconds := currentTime.UnixNano() / int64(time.Millisecond)

View File

@ -158,7 +158,8 @@ create table "meeting_lockSettings" (
"hideUserList" boolean,
"lockOnJoin" boolean,
"lockOnJoinConfigurable" boolean,
"hideViewersCursor" boolean
"hideViewersCursor" boolean,
"hideViewersAnnotation" boolean
);
create index "idx_meeting_lockSettings_meetingId" on "meeting_lockSettings"("meetingId");
@ -172,6 +173,7 @@ SELECT
mls."disableNotes",
mls."hideUserList",
mls."hideViewersCursor",
mls."hideViewersAnnotation",
mup."webcamsOnlyForModerator",
CASE WHEN
mls."disableCam" IS TRUE THEN TRUE
@ -181,6 +183,7 @@ SELECT
WHEN mls."disableNotes" IS TRUE THEN TRUE
WHEN mls."hideUserList" IS TRUE THEN TRUE
WHEN mls."hideViewersCursor" IS TRUE THEN TRUE
WHEN mls."hideViewersAnnotation" IS TRUE THEN TRUE
WHEN mup."webcamsOnlyForModerator" IS TRUE THEN TRUE
ELSE FALSE
END "hasActiveLockSetting"

View File

@ -18,6 +18,7 @@ select_permissions:
- hasActiveLockSetting
- hideUserList
- hideViewersCursor
- hideViewersAnnotation
- webcamsOnlyForModerator
filter:
meetingId:

View File

@ -6,6 +6,16 @@ configuration:
custom_column_names: {}
custom_name: pres_annotation_curr
custom_root_fields: {}
object_relationships:
- name: user
using:
manual_configuration:
column_mapping:
userId: userId
insertion_order: null
remote_table:
name: v_user_ref
schema: public
select_permissions:
- role: bbb_client
permission:
@ -18,5 +28,26 @@ select_permissions:
- annotationInfo
- lastUpdatedAt
filter:
meetingId:
_eq: X-Hasura-MeetingId
_and:
- meetingId:
_eq: X-Hasura-UserId
- _or:
- meetingId:
_eq: X-Hasura-ModeratorInMeeting
- userId:
_eq: X-Hasura-LockedUserId
- meetingId:
_neq: X-Hasura-LockedInMeeting
- _exists:
_table:
name: v_meeting_lockSettings
schema: public
_where:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- hideViewersAnnotation:
_eq: false
- user:
isModerator:
_eq: true

View File

@ -6,6 +6,16 @@ configuration:
custom_column_names: {}
custom_name: pres_annotation_history_curr
custom_root_fields: {}
object_relationships:
- name: user
using:
manual_configuration:
column_mapping:
userId: userId
insertion_order: null
remote_table:
name: v_user_ref
schema: public
select_permissions:
- role: bbb_client
permission:
@ -17,5 +27,26 @@ select_permissions:
- sequence
- annotationInfo
filter:
meetingId:
_eq: X-Hasura-MeetingId
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- _or:
- meetingId:
_eq: X-Hasura-ModeratorInMeeting
- userId:
_eq: X-Hasura-LockedUserId
- meetingId:
_neq: X-Hasura-LockedInMeeting
- _exists:
_table:
name: v_meeting_lockSettings
schema: public
_where:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- hideViewersAnnotation:
_eq: false
- user:
isModerator:
_eq: true

View File

@ -28,5 +28,26 @@ select_permissions:
- xPercent
- yPercent
filter:
meetingId:
_eq: X-Hasura-MeetingId
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- _or:
- user:
isModerator:
_eq: true
- meetingId:
_eq: X-Hasura-ModeratorInMeeting
- meetingId:
_neq: X-Hasura-LockedInMeeting
- userId:
_eq: X-Hasura-LockedUserId
- _exists:
_table:
name: v_meeting_lockSettings
schema: public
_where:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- hideViewersCursor:
_eq: false

View File

@ -85,6 +85,7 @@ const ChatMesssage: React.FC<ChatMessageProps> = ({
const sameSender = (previousMessage?.user?.userId
|| lastSenderPreviousPage) === message?.user?.userId;
const isSystemSender = message.messageType === ChatMessageType.BREAKOUT_ROOM;
const dateTime = new Date(message?.createdAt);
const messageContent: {
name: string,
@ -123,12 +124,26 @@ const ChatMesssage: React.FC<ChatMessageProps> = ({
/>
),
};
case ChatMessageType.BREAKOUT_ROOM:
return {
name: message.senderName,
color: '#0F70D7',
isModerator: true,
isSystemSender: true,
component: (
<ChatMessageTextContent
emphasizedMessage
text={message.message}
/>
),
};
case ChatMessageType.TEXT:
default:
return {
name: message.user?.name,
color: message.user?.color,
isModerator: message.user?.isModerator,
isSystemSender: ChatMessageType.BREAKOUT_ROOM,
component: (
<ChatMessageTextContent
emphasizedMessage={message?.user?.isModerator}
@ -139,14 +154,14 @@ const ChatMesssage: React.FC<ChatMessageProps> = ({
}
}, []);
return (
<ChatWrapper sameSender={sameSender} ref={messageRef}>
<ChatWrapper isSystemSender={isSystemSender} sameSender={sameSender} ref={messageRef}>
{(!message?.user || !sameSender) && (
<ChatAvatar
avatar={message.user?.avatar}
color={messageContent.color}
moderator={messageContent.isModerator}
>
{message.user?.avatar.length === 0 ? messageContent.name.toLowerCase().slice(0, 2) || '' : ''}
{!message.user || message.user?.avatar.length === 0 ? messageContent.name.toLowerCase().slice(0, 2) || 'q' : 'a'}
</ChatAvatar>
)}
<ChatContent sameSender={message?.user ? sameSender : false}>

View File

@ -17,6 +17,7 @@ import {
interface ChatWrapperProps {
sameSender: boolean;
isSystemSender: boolean;
}
interface ChatContentProps {
@ -50,6 +51,12 @@ export const ChatWrapper = styled.div<ChatWrapperProps>`
margin: ${borderSize} ${borderSize} 0 0;
}
font-size: ${fontSizeBase};
${({ isSystemSender }) => isSystemSender && `
background-color: #fef9f1;
border-left: 2px solid #f5c67f;
border-radius: 0px 3px 3px 0px;
padding: 8px 2px;
`}
`;
export const ChatContent = styled.div<ChatContentProps>`
@ -118,7 +125,7 @@ export const ChatAvatar = styled.div<ChatAvatarProps>`
${({ moderator }) => moderator && `
border-radius: 5px;
`}
// ================ image ================
${({ avatar, emoji, color }) => avatar?.length !== 0 && !emoji && css`
background-image: url(${avatar});
@ -136,7 +143,7 @@ export const ChatAvatar = styled.div<ChatAvatarProps>`
justify-content: center;
align-items:center;
// ================ content ================
& .react-loading-skeleton {
height: 2.25rem;
width: 2.25rem;

View File

@ -27,6 +27,8 @@ export const CHAT_MESSAGE_PUBLIC_SUBSCRIPTION = gql`
messageId
createdAt
messageMetadata
senderName
senderRole
}
}
`;

View File

@ -10,5 +10,6 @@ export const enum ChatMessageType {
TEXT = 'default',
POLL = 'poll',
PRESENTATION = 'presentation',
CHAT_CLEAR = 'publicChatHistoryCleared'
CHAT_CLEAR = 'publicChatHistoryCleared',
BREAKOUT_ROOM = 'breakoutRoomModeratorMsg',
}

View File

@ -434,6 +434,7 @@ exports.dropAreaLeft = 'div[data-test="dropArea-contentLeft"]';
exports.dropAreaRight = 'div[data-test="dropArea-contentRight"]';
exports.dropAreaTop = 'div[data-test="dropArea-contentTop"]';
exports.dropAreaSidebarBottom = 'div[data-test="dropArea-sidebarContentBottom"]';
exports.selfViewDisableBtn = 'li[data-test="selfViewDisableBtn"]';
exports.videoQualitySelector = 'select[id="setQuality"]';
exports.webcamItemTalkingUser = 'div[data-test="webcamItemTalkingUser"]';

View File

@ -197,7 +197,7 @@ test.describe.parallel('User', () => {
});
// https://docs.bigbluebutton.org/2.6/release-tests.html#see-other-viewers-in-the-users-list
test('Lock See other viewers in the Users list', async ({ browser, context, page }) => {
test('Lock See other viewers in the Users list @flaky', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.lockSeeOtherViewersUserList();

View File

@ -86,6 +86,23 @@ class Webcam extends Page {
await expect(height).toBe(windowHeight);
}
async disableSelfView() {
await this.waitAndClick(e.joinVideo);
await this.waitForSelector(e.noneBackgroundButton);
await uploadBackgroundVideoImage(this);
await this.waitAndClick(e.selectCustomBackground);
await sleep(1000);
await this.waitAndClick(e.startSharingWebcam);
await this.waitForSelector(e.webcamContainer);
await this.waitAndClick(e.dropdownWebcamButton);
await this.waitAndClick(e.selfViewDisableBtn);
const webcamVideoLocator = await this.getLocator(e.webcamConnecting);
await expect(webcamVideoLocator).toHaveScreenshot('disable-self-view.png');
}
async managingNewBackground() {
await this.waitAndClick(e.joinVideo);
await this.waitForSelector(e.noneBackgroundButton);

View File

@ -42,6 +42,12 @@ test.describe.parallel('Webcam', () => {
await webcam.webcamFullscreen();
});
test('Disable Self-view', async ({ browser, page }) => {
const webcam = new Webcam(browser, page);
await webcam.init(true, true);
await webcam.disableSelfView();
});
test.describe('Webcam background', () => {
/* this test has the flaky tag because it is breaking due to a default video from chrome that
is overlapping the virtual background. */

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -84,6 +84,7 @@ class ConnectionController {
"X-Hasura-Role" "bbb_client"
"X-Hasura-Locked" u.locked ? "true" : "false"
"X-Hasura-LockedInMeeting" u.locked ? userSession.meetingID : ""
"X-Hasura-LockedUserId" u.locked ? userSession.internalUserId : ""
"X-Hasura-ModeratorInMeeting" u.isModerator() ? userSession.meetingID : ""
"X-Hasura-PresenterInMeeting" u.isPresenter() ? userSession.meetingID : ""
"X-Hasura-UserId" userSession.internalUserId