Merge pull request #15390 from ramonlsouza/merge25-26-jul14

chore: Merge v2.5.3 into v2.6
This commit is contained in:
Anton Georgiev 2022-07-15 11:43:17 -04:00 committed by GitHub
commit f2e25768c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 799 additions and 649 deletions

View File

@ -83,7 +83,7 @@ public class ThumbnailCreatorImp implements ThumbnailCreator {
COMMAND = IMAGEMAGICK_DIR + File.separatorChar + "convert -thumbnail 150x150 " + source + " " + dest;
} else {
dest = thumbsDir.getAbsolutePath() + File.separatorChar + TEMP_THUMB_NAME + "-" + page; // the "-x.png" is appended automagically
COMMAND = "pdftocairo -png -scale-to 150 " + source + " " + dest;
COMMAND = "pdftocairo -png -scale-to 150 -cropbox " + source + " " + dest;
}
//System.out.println(COMMAND);

View File

@ -53,6 +53,24 @@ export function getSumOfTime(eventsArr) {
}, 0);
}
export function getJoinTime(eventsArr) {
return eventsArr.reduce((prevVal, elem) => {
if (prevVal === 0 || elem.registeredOn < prevVal) {
return elem.registeredOn;
}
return prevVal;
}, 0);
}
export function getLeaveTime(eventsArr) {
return eventsArr.reduce((prevVal, elem) => {
if (elem.leftOn > prevVal) {
return elem.leftOn;
}
return prevVal;
}, 0);
}
export function tsToHHmmss(ts) {
return (new Date(ts).toISOString().substr(11, 8));
}
@ -121,6 +139,8 @@ export function makeUserCSVData(users, polls, intl) {
const user = userValues[i];
const webcam = getSumOfTime(user.webcams);
const duration = getSumOfTime(Object.values(user.intIds));
const joinTime = getJoinTime(Object.values(user.intIds));
const leaveTime = getLeaveTime(Object.values(user.intIds));
const userData = {
name: user.name,
@ -138,7 +158,7 @@ export function makeUserCSVData(users, polls, intl) {
raiseHand: filterUserEmojis(user, 'raiseHand').length,
answers: Object.keys(user.answers).length,
emojis: filterUserEmojis(user, skipEmojis).length,
registeredOn: intl.formatDate(user.registeredOn, {
registeredOn: intl.formatDate(joinTime, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
@ -146,14 +166,14 @@ export function makeUserCSVData(users, polls, intl) {
minute: '2-digit',
second: '2-digit',
}),
leftOn: intl.formatDate(user.leftOn, {
leftOn: leaveTime > 0 ? intl.formatDate(leaveTime, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}),
}) : '-',
duration: tsToHHmmss(duration),
};

View File

@ -1 +1 @@
git clone --branch v1.2.1 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
git clone --branch v1.2.2 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads

View File

@ -276,7 +276,7 @@ usage() {
echo "Configuration:"
echo " --version Display BigBlueButton version (packages)"
echo " --setip <IP/hostname> Set IP/hostname for BigBlueButton"
echo " --setsecret <secret> Change the shared secret in bigbluebutton.properties"
echo " --setsecret <secret> Change the shared secret in /etc/bigbluebutton/bbb-web.properties"
echo " --set-port-range MIN-MAX Change UDP port range used for audio/video/screenshare"
echo
echo "Monitoring:"
@ -377,17 +377,17 @@ start_bigbluebutton () {
if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then
TOMCAT_SERVICE=$TOMCAT_USER
systemctl start $TOMCAT_SERVICE || {
echo
echo "# Warning: $TOMCAT_SERVICE could not be started. Please, check BBB-LTI."
echo "# Run the command:"
echo "# sudo journalctl -u $TOMCAT_SERVICE"
echo "# To better understand the ERROR"
}
fi
systemctl start bigbluebutton.target
systemctl start $TOMCAT_SERVICE || {
echo
echo "# Warning: $TOMCAT_SERVICE could not be started. Please, check BBB-LTI."
echo "# Run the command:"
echo "# sudo journalctl -u $TOMCAT_SERVICE"
echo "# To better understand the ERROR"
}
systemctl start bigbluebutton.target nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-fsesl-akka bbb-rap-resque-worker bbb-rap-starter.service bbb-rap-caption-inbox.service $HTML5 $WEBHOOKS $ETHERPAD $PADS $BBB_WEB $BBB_LTI
if [ -f /usr/lib/systemd/system/bbb-html5.service ]; then
systemctl start mongod

View File

@ -1,4 +1,5 @@
import addSystemMsg from '../../../group-chat-msg/server/modifiers/addSystemMsg';
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
export default function sendPollChatMsg({ body }, meetingId) {
const { poll } = body;
@ -10,9 +11,14 @@ export default function sendPollChatMsg({ body }, meetingId) {
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
const pollResultData = poll;
const answers = pollResultData.answers.reduce(caseInsensitiveReducer, []);
const extra = {
type: 'poll',
pollResultData,
pollResultData: {
...pollResultData,
answers,
},
};
const payload = {

View File

@ -32,10 +32,6 @@ const intlMessages = defineMessages({
id: 'app.actionsBar.actionsDropdown.desktopShareLabel',
description: 'Desktop Share option label',
},
lockedDesktopShareLabel: {
id: 'app.actionsBar.actionsDropdown.lockedDesktopShareLabel',
description: 'Desktop locked Share option label',
},
stopDesktopShareLabel: {
id: 'app.actionsBar.actionsDropdown.stopDesktopShareLabel',
description: 'Stop Desktop Share option label',
@ -158,11 +154,10 @@ const ScreenshareButton = ({
</Styled.ScreenShareModal>,
);
const screenshareLocked = screenshareDataSavingSetting
? intlMessages.desktopShareLabel : intlMessages.lockedDesktopShareLabel;
const screenshareLabel = intlMessages.desktopShareLabel;
const vLabel = isVideoBroadcasting
? intlMessages.stopDesktopShareLabel : screenshareLocked;
? intlMessages.stopDesktopShareLabel : screenshareLabel;
const vDescr = isVideoBroadcasting
? intlMessages.stopDesktopShareDesc : intlMessages.desktopShareDesc;
@ -171,13 +166,12 @@ const ScreenshareButton = ({
&& ( !isMobile || isMobileApp)
&& amIPresenter;
const dataTest = !screenshareDataSavingSetting ? 'screenshareLocked'
: isVideoBroadcasting ? 'stopScreenShare' : 'startScreenShare';
const dataTest = isVideoBroadcasting ? 'stopScreenShare' : 'startScreenShare';
return shouldAllowScreensharing
? (
<Button
disabled={(!isMeteorConnected && !isVideoBroadcasting) || !screenshareDataSavingSetting}
disabled={(!isMeteorConnected && !isVideoBroadcasting)}
icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'}
data-test={dataTest}
label={intl.formatMessage(vLabel)}

View File

@ -26,6 +26,7 @@ const AudioModalButton = styled(Button)`
// Modifies the audio button icon colour
& span:first-child {
display: inline-block;
color: #1b3c4b;
background-color: #f1f8ff;
box-shadow: none;
@ -46,6 +47,7 @@ const AudioModalButton = styled(Button)`
// Modifies the button label text
& span:last-child {
display: block;
color: black;
font-size: 1rem;
font-weight: 600;

View File

@ -2,9 +2,9 @@ import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import _ from 'lodash';
import BBBMenu from "/imports/ui/components/common/menu/component";
import BBBMenu from '/imports/ui/components/common/menu/component';
import { getDateString } from '/imports/utils/string-utils';
import Trigger from "/imports/ui/components/common/control-header/right/component";
import Trigger from '/imports/ui/components/common/control-header/right/component';
import ChatService from '../service';
import { addNewAlert } from '../../screenreader-alert/service';

View File

@ -97,7 +97,7 @@ const Adapter = () => {
more info: https://github.com/bigbluebutton/bigbluebutton/issues/11842 */
useEffect(() => {
if (users[Auth.meetingID] && users[Auth.meetingID][Auth.userID]) {
if (currentUserData?.role !== users[Auth.meetingID][Auth.userID].role) {
if (currentUserData?.role !== users[Auth.meetingID][Auth.userID]?.role) {
prevUserData = currentUserData;
}
currentUserData = users[Auth.meetingID][Auth.userID];

View File

@ -30,7 +30,15 @@ const Adapter = () => {
},
});
},
removed: () => {},
removed: (obj) => {
ChatLogger.debug('usersAdapter::observe::removed', obj);
dispatch({
type: ACTIONS.REMOVED,
value: {
user: obj,
},
});
},
});
}, []);

View File

@ -43,19 +43,32 @@ const reducer = (state, action) => {
ChatLogger.debug('UsersContextProvider::reducer::removed', { ...action });
const { user } = action.value;
if (state[user.meetingId][user.userId]) {
const stateUser = state[user.meetingId][user.userId];
if (stateUser) {
const newState = { ...state };
delete newState[user.meetingId][user.userId];
newState[user.meetingId][user.userId] = {
...stateUser,
loggedOut: true,
};
return newState;
}
return state;
return state;
}
// USER PERSISTENT DATA
case ACTIONS.ADDED_USER_PERSISTENT_DATA: {
const { user } = action.value;
if (state[user.meetingId] && state[user.meetingId][user.userId]) {
if (state[user.meetingId][user.userId].loggedOut) {
const newState = { ...state };
newState[user.meetingId][user.userId] = {
...state[user.meetingId][user.userId],
loggedOut: false,
};
return newState;
}
return state;
}

View File

@ -627,6 +627,7 @@ class ConnectionStatusComponent extends PureComponent {
<Styled.Copy
disabled={!hasNetworkData}
role="button"
data-test="copyStats"
onClick={this.copyNetworkData.bind(this)}
onKeyPress={this.copyNetworkData.bind(this)}
tabIndex={0}

View File

@ -5,6 +5,10 @@ import logger from '/imports/startup/client/logger';
const Cursor = new Mongo.Collection(null);
let cursorStreamListener = null;
export const clearCursors = () => {
Cursor.remove({});
};
function updateCursor(userId, payload) {
const selector = {
userId,

View File

@ -5,6 +5,7 @@ import Settings from '/imports/ui/services/settings';
import getFromUserSettings from '/imports/ui/services/users-settings';
import { isExternalVideoEnabled, isScreenSharingEnabled } from '/imports/ui/services/features';
import { ACTIONS } from '../layout/enums';
import UserService from '/imports/ui/components/user-list/service';
const LAYOUT_CONFIG = Meteor.settings.public.layout;
const KURENTO_CONFIG = Meteor.settings.public.kurento;
@ -26,7 +27,7 @@ function shouldShowWhiteboard() {
function shouldShowScreenshare() {
const { viewScreenshare } = Settings.dataSaving;
return isScreenSharingEnabled() && viewScreenshare && isVideoBroadcasting();
return isScreenSharingEnabled() && (viewScreenshare || UserService.isUserPresenter()) && isVideoBroadcasting();
}
function shouldShowExternalVideo() {

View File

@ -20,10 +20,6 @@ const PollContainer = ({ ...props }) => {
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;
const amIPresenter = users[Auth.meetingID][Auth.userID].presenter;
const isPollSecret = Session.get('secretPoll') || false;
Meteor.subscribe('current-poll', isPollSecret, amIPresenter);
const usernames = {};
@ -35,16 +31,18 @@ const PollContainer = ({ ...props }) => {
<Poll
{...{ layoutContextDispatch, sidebarContentPanel, ...props }}
usernames={usernames}
amIPresenter={amIPresenter}
/>
);
};
export default withTracker(() => {
export default withTracker(({ amIPresenter }) => {
const isPollSecret = Session.get('secretPoll') || false;
const currentPresentation = Presentations.findOne({
current: true,
}, { fields: { podId: 1 } }) || {};
Meteor.subscribe('current-poll', isPollSecret, amIPresenter);
const currentSlide = PresentationService.getCurrentSlide(currentPresentation.podId);
const pollId = currentSlide ? currentSlide.id : PUBLIC_CHAT_KEY;
@ -58,6 +56,7 @@ export default withTracker(() => {
const stopPoll = () => makeCall('stopPoll');
return {
isPollSecret,
currentSlide,
pollTypes,
startPoll,

View File

@ -1,7 +1,6 @@
import Auth from '/imports/ui/services/auth';
import { CurrentPoll } from '/imports/api/polls';
import { escapeHtml } from '/imports/utils/string-utils';
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
import { defineMessages } from 'react-intl';
const POLL_AVATAR_COLOR = '#3B48A9';
@ -89,7 +88,7 @@ const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => {
answers.map((item) => {
responded += item.numVotes;
return item;
}).reduce(caseInsensitiveReducer, []).forEach((item) => {
}).forEach((item) => {
const numResponded = responded === numRespondents ? numRespondents : responded;
const pct = Math.round((item.numVotes / numResponded) * 100);
const pctBars = '|'.repeat((pct * MAX_POLL_RESULT_BARS) / 100);

View File

@ -26,6 +26,7 @@ import DEFAULT_VALUES from '../layout/defaultValues';
import { colorContentBackground } from '/imports/ui/stylesheets/styled-components/palette';
import browserInfo from '/imports/utils/browserInfo';
import { addNewAlert } from '../screenreader-alert/service';
import { clearCursors } from '/imports/ui/components/cursor/service';
const intlMessages = defineMessages({
presentationLabel: {
@ -163,6 +164,8 @@ class Presentation extends PureComponent {
presentationBounds,
numCameras,
intl,
multiUser,
clearFakeAnnotations,
} = this.props;
const { presentationWidth, presentationHeight } = this.state;
@ -170,8 +173,14 @@ class Presentation extends PureComponent {
const {
numCameras: prevNumCameras,
presentationBounds: prevPresentationBounds,
multiUser: prevMultiUser,
} = prevProps;
if (prevMultiUser && !multiUser) {
clearFakeAnnotations();
clearCursors();
}
if (numCameras !== prevNumCameras) {
this.onResize();
}

View File

@ -138,5 +138,6 @@ export default lockContextContainer(
removeWhiteboardGlobalAccess: WhiteboardService.removeGlobalAccess,
multiUserSize: WhiteboardService.getMultiUserSize(currentSlide?.id),
isViewersCursorLocked,
clearFakeAnnotations: WhiteboardService.clearFakeAnnotations,
};
})(PresentationContainer));

View File

@ -6,6 +6,7 @@ import lockContextContainer from "/imports/ui/components/lock-viewers/context/co
import { UsersContext } from "/imports/ui/components/components-data/users-context/context";
import CursorService from "./service";
import Cursor from "./component";
import WhiteboardService from "/imports/ui/components/whiteboard/service";
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
@ -34,10 +35,12 @@ const CursorContainer = (props) => {
export default lockContextContainer(
withTracker((params) => {
const { cursorId, userLocks } = params;
const { cursorId, userLocks, whiteboardId, presenter } = params;
const isViewersCursorLocked = userLocks?.hideViewersCursor;
const cursor = CursorService.getCurrentCursor(cursorId);
if (cursor) {
const hasPermission = presenter || WhiteboardService.hasMultiUserAccess(whiteboardId, cursor.userId);
if (cursor&& hasPermission) {
const {
xPercent: cursorX,
yPercent: cursorY,

View File

@ -5,6 +5,7 @@ import { toPng } from 'html-to-image';
import { toast } from 'react-toastify';
import logger from '/imports/startup/client/logger';
import Styled from './styles';
import BBBMenu from "/imports/ui/components/common/menu/component";
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
import { ACTIONS } from '/imports/ui/components/layout/enums';
import browserInfo from '/imports/utils/browserInfo';
@ -93,6 +94,7 @@ const PresentationMenu = (props) => {
layoutContextDispatch,
meetingName,
isIphone,
isRTL
} = props;
const [state, setState] = useState({
@ -260,46 +262,34 @@ const PresentationMenu = (props) => {
return (
<Styled.Right>
<TooltipContainer title={intl.formatMessage(intlMessages.optionsLabel)}>
<Styled.DropdownButton
state={isDropdownOpen ? 'open' : 'closed'}
aria-label={intl.formatMessage(intlMessages.optionsLabel)}
data-test="whiteboardOptionsButton"
onClick={() => setIsDropdownOpen((isOpen) => !isOpen)}
>
<Styled.ButtonIcon iconName="more" />
</Styled.DropdownButton>
</TooltipContainer>
{ isDropdownOpen && (
<>
<Styled.Overlay onClick={() => setIsDropdownOpen(false)} />
<Styled.Dropdown
ref={dropdownRef}
onBlur={() => setIsDropdownOpen(false)}
tabIndex={0}
>
<Styled.List>
{ options.map((option) => {
const {
label, onClick, key, dataTest,
} = option;
return (
<Styled.ListItem
{...{
onClick,
key,
'data-test': dataTest ?? '',
}}
>
{label}
</Styled.ListItem>
);
}) }
</Styled.List>
</Styled.Dropdown>
</>
) }
<BBBMenu
trigger={
<TooltipContainer title={intl.formatMessage(intlMessages.optionsLabel)}>
<Styled.DropdownButton
state={isDropdownOpen ? 'open' : 'closed'}
aria-label={intl.formatMessage(intlMessages.optionsLabel)}
data-test="whiteboardOptionsButton"
onClick={() => {
setIsDropdownOpen((isOpen) => !isOpen)
}}
>
<Styled.ButtonIcon iconName="more" />
</Styled.DropdownButton>
</TooltipContainer>
}
opts={{
id: "default-dropdown-menu",
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getContentAnchorEl: null,
fullwidth: "true",
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
container: fullscreenRef
}}
actions={getAvailableOptions()}
/>
</Styled.Right>
);
};

View File

@ -12,6 +12,7 @@ const PresentationMenuContainer = (props) => {
const layoutContextDispatch = layoutDispatch();
const { elementId } = props;
const isFullscreen = currentElement === elementId;
const isRTL = layoutSelect((i) => i.isRTL);
return (
<PresentationMenu
@ -21,6 +22,7 @@ const PresentationMenuContainer = (props) => {
currentGroup,
isFullscreen,
layoutContextDispatch,
isRTL,
}}
/>
);

View File

@ -123,54 +123,11 @@ const Line = styled.div`
padding: ${lgPaddingX} 0;
`;
const List = styled.ul`
list-style-type: none;
padding: ${mdPaddingY} ${borderSize};
margin: 0;
white-space: nowrap;
text-align: left;
[dir="rtl"] & {
text-align: right;
}
`;
const ListItem = styled.li`
padding: ${mdPaddingY} ${mdPaddingX};
&:hover {
background-color: ${colorPrimary};
color: white;
}
`;
const Dropdown = styled.div`
position: absolute;
right: 0;
top: 117%;
background-color: ${colorWhite};
z-index: 1000;
box-shadow: 0 0 10px 1px ${colorGrayLightest};
border-radius: ${borderRadius};
[dir="rtl"] & {
right: auto;
left: 0;
}
`;
const ButtonIcon = styled(Icon)`
width: 1em;
text-align: center;
`;
const Overlay = styled.div`
position: fixed;
inset: 0;
z-index: 999;
cursor: auto;
`;
export default {
DropdownButton,
Right,
@ -178,9 +135,5 @@ export default {
StatusIcon,
ToastIcon,
Line,
List,
Dropdown,
ListItem,
ButtonIcon,
Overlay,
};

View File

@ -399,6 +399,7 @@ class PresentationToolbar extends PureComponent {
) : null}
<Styled.FitToWidthButton
role="button"
data-test="fitToWidthButton"
aria-describedby={fitToWidth ? 'fitPageDesc' : 'fitWidthDesc'}
aria-label={
fitToWidth

View File

@ -47,6 +47,7 @@ const SidebarContent = (props) => {
resizableEdge,
contextDispatch,
sidebarContentPanel,
amIPresenter,
} = props;
const [resizableWidth, setResizableWidth] = useState(width);
@ -141,7 +142,7 @@ const SidebarContent = (props) => {
{sidebarContentPanel === PANELS.BREAKOUT && <BreakoutRoomContainer />}
{sidebarContentPanel === PANELS.WAITING_USERS && <WaitingUsersPanel />}
<Styled.Poll style={{ minWidth, top: '0', display: pollDisplay }} id="pollPanel">
<PollContainer smallSidebar={smallSidebar} />
<PollContainer smallSidebar={smallSidebar} amIPresenter={amIPresenter} />
</Styled.Poll>
</Resizable>
);

View File

@ -1,6 +1,8 @@
import React from 'react';
import React, { useContext } from 'react';
import SidebarContent from './component';
import { layoutSelectInput, layoutSelectOutput, layoutDispatch } from '../layout/context';
import { UsersContext } from '../components-data/users-context/context';
import Auth from '/imports/ui/services/auth';
const SidebarContentContainer = () => {
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
@ -10,11 +12,16 @@ const SidebarContentContainer = () => {
if (sidebarContentOutput.display === false) return null;
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;
const amIPresenter = users[Auth.meetingID][Auth.userID].presenter;
return (
<SidebarContent
{...sidebarContentOutput}
contextDispatch={layoutContextDispatch}
sidebarContentPanel={sidebarContentPanel}
amIPresenter={amIPresenter}
/>
);
};

View File

@ -632,7 +632,7 @@ const sortUsersByLastName = (a, b) => {
return sortUsersByName(aUser, bUser);
};
const isUserPresenter = (userId) => {
const isUserPresenter = (userId = Auth.userID) => {
const user = Users.findOne({ userId },
{ fields: { presenter: 1 } });
return user ? user.presenter : false;

View File

@ -20,7 +20,7 @@ const clearPreview = (annotation) => {
UnsentAnnotations.remove({ id: annotation });
};
function clearFakeAnnotations() {
const clearFakeAnnotations = () => {
UnsentAnnotations.remove({});
Annotations.remove({ id: /-fake/g });
}
@ -396,5 +396,5 @@ export {
getCurrentPres,
removeShapes,
changeCurrentSlide,
getCurSlide,
clearFakeAnnotations,
};

View File

@ -49,5 +49,5 @@ export function isLayoutsEnabled() {
}
export function isVirtualBackgroundsEnabled() {
return getDisabledFeatures().indexOf('virtualBackgrounds') === -1;
return getDisabledFeatures().indexOf('virtualBackgrounds') === -1 && Meteor.settings.public.virtualBackgrounds.enabled;
}

View File

@ -34,7 +34,6 @@ const MODELS = {
};
const {
enabled: VIRTUAL_BACKGROUND_ENABLED = true,
thumbnailsPath: THUMBNAILS_PATH = '/resources/images/virtual-backgrounds/thumbnails/',
fileNames: IMAGE_NAMES = ['home.jpg', 'coffeeshop.jpg', 'board.jpg'],
storedOnBBB: IS_STORED_ON_BBB = true,

View File

@ -454,7 +454,7 @@
"app.settings.main.save.label.description": "Saves the changes and closes the settings menu",
"app.settings.dataSavingTab.label": "Data savings",
"app.settings.dataSavingTab.webcam": "Enable other participants webcams",
"app.settings.dataSavingTab.screenShare": "Enable desktop sharing",
"app.settings.dataSavingTab.screenShare": "Enable other participants desktop sharing",
"app.settings.dataSavingTab.description": "To save your bandwidth adjust what's currently being displayed.",
"app.settings.save-notification.label": "Settings have been saved",
"app.statusNotifier.lowerHands": "Lower Hands",
@ -474,7 +474,6 @@
"app.actionsBar.actionsDropdown.presentationLabel": "Manage presentations",
"app.actionsBar.actionsDropdown.initPollLabel": "Initiate a poll",
"app.actionsBar.actionsDropdown.desktopShareLabel": "Share your screen",
"app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Screenshare locked",
"app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Stop sharing your screen",
"app.actionsBar.actionsDropdown.presentationDesc": "Upload your presentation",
"app.actionsBar.actionsDropdown.initPollDesc": "Initiate a poll",

File diff suppressed because it is too large Load Diff

View File

@ -1046,7 +1046,7 @@
"app.learningDashboard.statusTimelineTable.thumbnail": "Vignette de présentation",
"app.learningDashboard.errors.invalidToken": "Jeton de session invalide",
"app.learningDashboard.errors.dataUnavailable": "Les données ne sont plus disponibles",
"mobileApp.portals.list.empty.addFirstPortal.label": "Ajoutez votre premier portail en utilisant le bouton ci-dessus,",
"mobileApp.portals.list.empty.addFirstPortal.label": "Ajoutez votre premier portail en utilisant le bouton ci-dessus",
"mobileApp.portals.list.empty.orUseOurDemoServer.label": "ou utilisez notre serveur de démo.",
"mobileApp.portals.list.add.button.label": "Ajouter un portail",
"mobileApp.portals.fields.name.label": "Nom du portail",

View File

@ -1,7 +1,7 @@
{
"app.home.greeting": "Vaša prezentacija započet će uskoro ...",
"app.chat.submitLabel": "Pošalji poruku",
"app.chat.inputPlaceholder": "Poruke [0]",
"app.chat.inputPlaceholder": "Poruke {0}",
"app.chat.titlePublic": "Javni chat",
"app.chat.titlePrivate": "Privatni chat s {0}",
"app.chat.partnerDisconnected": "{0} je napustio sastanak",
@ -105,7 +105,7 @@
"app.meeting.meetingTimeRemaining": "Preostalo vrijeme sastanka: {0}",
"app.meeting.alertMeetingEndsUnderMinutesSingular": "Sastanak prestaje za jednu minutu.",
"app.meeting.alertMeetingEndsUnderMinutesPlural": "Sastanak prestaje za {0} minuta/e.",
"app.meeting.alertBreakoutEndsUnderMinutesPlural": "Breakout soba se zatvara za [0] minuta/e.",
"app.meeting.alertBreakoutEndsUnderMinutesPlural": "Breakout soba se zatvara za {0} minuta/e.",
"app.meeting.alertBreakoutEndsUnderMinutesSingular": "Breakout soba se zatvara za 1 minutu.",
"app.presentation.hide": "Skrij prezentaciju",
"app.presentation.notificationLabel": "Trenutačna prezentacija",
@ -138,7 +138,7 @@
"app.presentationUploder.tableHeading.filename": "Datoteka",
"app.presentationUploder.tableHeading.options": "Postavke",
"app.presentationUploder.tableHeading.status": "Status",
"app.presentationUploder.uploading": "Prijenos na poslužitelj [0] [1]",
"app.presentationUploder.uploading": "Prijenos na poslužitelj {0} {1}",
"app.presentationUploder.item" : "Stavka",
"app.presentationUploder.itemPlural" : "Stavke",
"app.presentationUploder.clearErrors": "Ukloni pogreške",
@ -188,7 +188,7 @@
"app.polling.pollQuestionTitle": "Anketno pitanje",
"app.polling.submitLabel": "Predaj",
"app.polling.responsePlaceholder": "Unesite odgovor",
"app.polling.pollAnswerLabel": "Odgovor na anketu [0]",
"app.polling.pollAnswerLabel": "Odgovor na anketu {0}",
"app.downloadPresentationButton.label": "Preuzmi originalnu prezentaciju",
"app.connectingMessage": "Spajanje ...",
"app.retryNow": "Pokušajte ponovno",
@ -299,7 +299,7 @@
"app.actionsBar.emojiMenu.thumbsUpDesc": "Promijenite svoj status u 'Palac gore'",
"app.actionsBar.emojiMenu.thumbsDownLabel": "Palac dolje",
"app.actionsBar.emojiMenu.thumbsDownDesc": "Promijenite svoj status u 'Palac dolje'",
"app.actionsBar.currentStatusDesc": "trenutačni status [0]",
"app.actionsBar.currentStatusDesc": "trenutačni status {0}",
"app.audioNotification.closeLabel": "Zatvori",
"app.breakoutJoinConfirmation.title": "Priključi se u breakout sobu",
"app.breakoutJoinConfirmation.message": "Želite li se priključiti",

View File

@ -173,6 +173,7 @@
"app.presentation.options.fullscreen": "Tela cheia",
"app.presentation.options.exitFullscreen": "Sair de tela cheia",
"app.presentation.options.minimize": "Minimizar",
"app.presentation.options.snapshot": "Salvar imagem do slide atual",
"app.presentation.options.downloading": "Downloading...",
"app.presentation.options.downloaded": "Donwload da apresentação atual encerrado",
"app.presentation.options.downloadFailed": "Não foi possível fazer o download da apresentação atual",

View File

@ -1043,7 +1043,9 @@
"app.learningDashboard.errors.dataUnavailable": "Данные больше не доступны",
"mobileApp.portals.fields.url.label": "URL сервера",
"mobileApp.portals.addPortalPopup.confirm.button.label": "Сохранить",
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Имя уже используется"
"mobileApp.portals.addPortalPopup.validation.emptyFields": "Поля, обязательные для заполнения",
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Имя уже используется",
"mobileApp.portals.addPortalPopup.validation.urlInvalid": "Ошибка при загрузке страницы. Проверьте URL и Интернет соединение."
}

View File

@ -2,12 +2,14 @@ const { test } = require('@playwright/test');
const { Audio } = require('./audio');
test.describe.parallel('Audio', () => {
// https://docs.bigbluebutton.org/2.5/release-tests.html#listen-only-mode-automated
test('Join audio with Listen Only @ci', async ({ browser, page }) => {
const audio = new Audio(browser, page);
await audio.init(true, false);
await audio.joinAudio();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#join-audio-automated
test('Join audio with Microphone @ci', async ({ browser, page }) => {
const audio = new Audio(browser, page);
await audio.init(true, false);

View File

@ -9,6 +9,7 @@ test.describe.parallel('Breakout', () => {
await create.create();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#moderators-creating-breakout-rooms-and-assiging-users-automated
test('Join Breakout room @ci', async ({ browser, context, page }) => {
const join = new Join(browser, context);
await join.initPages(page);

View File

@ -3,12 +3,14 @@ const { Chat } = require('./chat');
const { PrivateChat } = require('./privateChat');
test.describe.parallel('Chat', () => {
// https://docs.bigbluebutton.org/2.5/release-tests.html#public-message-automated
test('Send public message @ci', async ({ browser, page }) => {
const chat = new Chat(browser, page);
await chat.init(true, true);
await chat.sendPublicMessage();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#private-message-automated
test('Send private message @ci', async ({ browser, context, page }) => {
const privateChat = new PrivateChat(browser, context);
await privateChat.initPages(page);
@ -34,12 +36,14 @@ test.describe.parallel('Chat', () => {
await chat.saveChat(testInfo);
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#chat-character-limit-automated
test('Verify character limit', async ({ browser, page }) => {
const chat = new Chat(browser, page);
await chat.init(true, true);
await chat.characterLimit();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#sending-empty-chat-message-automated
test('Not able to send an empty message', async ({ browser, page }) => {
const chat = new Chat(browser, page);
await chat.init(true, true);

View File

@ -3,7 +3,7 @@ const { MultiUsers } = require('../user/multiusers');
const e = require('../core/elements');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
const { openConnectionStatus, checkNetworkStatus } = require('./util');
const { sleep } = require('../core/helpers');
class ConnectionStatus extends MultiUsers {
constructor(browser, context) {
@ -38,6 +38,24 @@ class ConnectionStatus extends MultiUsers {
const status = this.modPage.getLocator(e.connectionStatusItemUser);
await expect(status).toHaveCount(1);
}
async linkToSettingsTest() {
await openConnectionStatus(this.modPage);
await this.modPage.page.evaluate(() => window.dispatchEvent(new CustomEvent('socketstats', { detail: { rtt: 2000 } })));
await this.modPage.hasElement(e.connectionStatusLinkToSettings);
await this.modPage.waitAndClick(e.connectionStatusLinkToSettings);
await this.modPage.waitForSelector(e.dataSavingsTab);
}
async copyStatsTest(context) {
await openConnectionStatus(this.modPage);
await this.modPage.hasElementEnabled(e.copyStats);
await this.modPage.waitAndClick(e.copyStats);
await context.grantPermissions(['clipboard-write', 'clipboard-read'], { origin: process.env.BBB_URL });
const copiedText = await this.modPage.page.evaluate(async () => navigator.clipboard.readText());
const check = copiedText.includes("audioCurrentUploadRate");
await expect(check).toBeTruthy();
}
}
exports.ConnectionStatus = ConnectionStatus;

View File

@ -19,4 +19,17 @@ test.describe.parallel('Connection Status', () => {
await connectionStatus.initModPage(page);
await connectionStatus.reportUserInConnectionIssues();
});
test('Go to settings modal', async ({ browser, context, page }) => {
const connectionStatus = new ConnectionStatus(browser, context);
await connectionStatus.initModPage(page);
await connectionStatus.linkToSettingsTest();
});
test('Copy stats', async ({ browser, context, page }, testInfo) => {
test.fixme(testInfo.project.use.headless, 'Only works in headed mode');
const connectionStatus = new ConnectionStatus(browser, context);
await connectionStatus.initModPage(page);
await connectionStatus.copyStatsTest(context);
});
});

View File

@ -155,6 +155,7 @@ exports.pollYesNoAbstentionBtn = 'button[data-test="pollYesNoAbstentionBtn"]';
exports.currentSlideImg = 'img[id="slide-background-shape_image"]';
exports.uploadPresentationFileName = 'uploadTest.png';
exports.presentationPlaceholderLabel = 'There is no currently active presentation';
exports.noPresentationLabel = 'There is no currently active presentation';
exports.startScreenSharing = 'button[data-test="startScreenShare"]';
exports.stopScreenSharing = 'button[data-test="stopScreenShare"]';
exports.managePresentations = 'li[data-test="managePresentations"]';
@ -179,9 +180,11 @@ exports.videoModalInput = 'input[id="video-modal-input"]';
exports.startShareVideoBtn = 'button[data-test="startNewVideo"]';
exports.videoPlayer = 'div[data-test="videoPlayer"]';
exports.presentationTitle = 'h1[data-test="presentationTitle"]';
exports.fitToWidthButton = 'button[data-test="fitToWidthButton"]';
// YouTube frame
exports.youtubeLink = 'https://www.youtube.com/watch?v=Hso8yLzkqj8&ab_channel=BigBlueButton';
exports.youtubeFrame = 'iframe[title^="YouTube"]';
// The title we match for here is the title of the test video specified by youtubeLink
exports.youtubeFrame = 'iframe[title~="GreenLight"]';
exports.ytFrameTitle = 'a[class^="ytp-title-link"]';
// Toasts
exports.statingUploadPresentationToast = 'To be uploaded ...';
@ -192,9 +195,11 @@ exports.presentationUploadedToast = 'Current presentation';
exports.languageSelector = 'select[id="langSelector"]';
exports.messageTitle = 'h2[data-test="messageTitle"]';
exports.notesTitle = 'h2[data-test="notesTitle"]';
exports.dataSavingsTab = 'span[id="dataSaving"]';
// User
const userAvatar = 'div[data-test="userAvatar"]';
const networkDataContainer = 'div[data-test="networkDataContainer"]';
exports.userAvatar = userAvatar;
exports.moderatorAvatar = 'div[data-test="moderatorAvatar"]';
exports.viewerAvatar = 'div[data-test="viewerAvatar"]';
@ -212,14 +217,16 @@ exports.userListToggleBtn = 'button[data-test="toggleUserList"]';
exports.mobileUser = 'span[data-test="mobileUser"]';
exports.connectionStatusBtn = 'button[data-test="connectionStatusButton"]';
exports.connectionStatusModal = 'div[data-test="connectionStatusModal"]';
exports.copyStats = 'span[data-test="copyStats"]';
exports.dataSavingScreenshare = 'input[data-test="dataSavingScreenshare"]';
exports.screenshareLocked = 'button[data-test="screenshareLocked"]';
exports.connectionStatusItemEmpty = 'div[data-test="connectionStatusItemEmpty"]';
exports.connectionStatusTab2 = 'div[data-tab="2"]';
exports.connectionStatusItemUser = 'div[data-test="connectionStatusItemUser"]';
exports.connectionStatusLinkToSettings = `${networkDataContainer} span[role="button"]`;
exports.dataSavingWebcams = 'input[data-test="dataSavingWebcams"]';
exports.connectionStatusOfflineUser = 'div[data-test="offlineUser"]';
exports.connectionDataContainer = 'div[data-test="networkDataContainer"]';
exports.connectionDataContainer = networkDataContainer;
exports.avatarsWrapperAvatar = 'div[data-test="avatarsWrapperAvatar"]';
exports.guestPolicyLabel = 'li[data-test="guestPolicyLabel"]';
exports.downloadUserNamesList = 'li[data-test="downloadUserNamesList"]';

View File

@ -16,9 +16,9 @@ async function createMeeting(params, customParameter) {
const meetingID = `random-${getRandomInt(1000000, 10000000).toString()}`;
const mp = params.moderatorPW;
const ap = params.attendeePW;
const query = customParameter !== undefined ? `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}&joinViaHtml5=true`
const query = customParameter !== undefined ? `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}`
+ `&allowStartStopRecording=true&${customParameter}&autoStartRecording=false&welcome=${params.welcome}`
: `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}&joinViaHtml5=true`
: `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}`
+ `&allowStartStopRecording=true&autoStartRecording=false&welcome=${params.welcome}`;
const apicall = `create${query}${params.secret}`;
const checksum = sha1(apicall);
@ -29,8 +29,8 @@ async function createMeeting(params, customParameter) {
function getJoinURL(meetingID, params, moderator, customParameter) {
const pw = moderator ? params.moderatorPW : params.attendeePW;
const query = customParameter !== undefined ? `fullName=${params.fullName}&joinViaHtml5=true&meetingID=${meetingID}&password=${pw}&${customParameter}`
: `fullName=${params.fullName}&joinViaHtml5=true&meetingID=${meetingID}&password=${pw}`;
const query = customParameter !== undefined ? `fullName=${params.fullName}&meetingID=${meetingID}&password=${pw}&${customParameter}`
: `fullName=${params.fullName}&meetingID=${meetingID}&password=${pw}`;
const apicall = `join${query}${params.secret}`;
const checksum = sha1(apicall);
return `${params.server}/join?${query}&checksum=${checksum}`;

View File

@ -167,7 +167,7 @@ class Page {
}
async hasElementEnabled(selector, timeout = ELEMENT_WAIT_TIME) {
const locator = this.getLocator(selector);
const locator = this.getLocator(`${selector}:not([disabled])`);
await expect(locator).toBeEnabled({ timeout });
}

View File

@ -10,7 +10,6 @@ class ChatNotifications extends MultiUsers {
}
async publicChatNotification() {
await util.waitAndClearDefaultPresentationNotification(this.modPage);
await openSettings(this.modPage);
await util.enableChatPopup(this.modPage);
await util.saveSettings(this.modPage);
@ -24,7 +23,6 @@ class ChatNotifications extends MultiUsers {
}
async privateChatNotification() {
await util.waitAndClearDefaultPresentationNotification(this.modPage);
await openSettings(this.modPage);
await util.enableChatPopup(this.modPage);
await util.saveSettings(this.modPage);

View File

@ -31,13 +31,13 @@ test.describe.parallel('Notifications', () => {
test.describe.parallel('Chat', () => {
test('Public Chat notification', async ({ browser, context, page }) => {
const chatNotifications = new ChatNotifications(browser, context);
await chatNotifications.initPages(page);
await chatNotifications.initPages(page, true);
await chatNotifications.publicChatNotification();
});
test('Private Chat notification', async ({ browser, context, page }) => {
const chatNotifications = new ChatNotifications(browser, context);
await chatNotifications.initPages(page);
await chatNotifications.initPages(page, true);
await chatNotifications.privateChatNotification();
});
});
@ -51,7 +51,7 @@ test.describe.parallel('Notifications', () => {
test('Presentation upload notification', async ({ browser, context, page }) => { // this test is unstable, there's an apparent timing issue around the visibility of smallToastMsg
const presenterNotifications = new PresenterNotifications(browser, context);
await presenterNotifications.initPages(page);
await presenterNotifications.initPages(page, true);
await presenterNotifications.fileUploaderNotification();
});

View File

@ -19,8 +19,7 @@ class PresenterNotifications extends MultiUsers {
}
async fileUploaderNotification() {
await util.waitAndClearDefaultPresentationNotification(this.modPage);
await utilPresentation.uploadPresentation(this.modPage, e.pdfFileName, UPLOAD_PDF_WAIT_TIME);
await utilPresentation.uploadSinglePresentation(this.modPage, e.pdfFileName, UPLOAD_PDF_WAIT_TIME);
await util.checkNotificationText(this.userPage, e.presentationUploadedToast);
}

View File

@ -14,7 +14,6 @@ class Polling extends MultiUsers {
}
async createPoll() {
await waitAndClearDefaultPresentationNotification(this.modPage);
await util.startPoll(this.modPage);
await this.modPage.hasElement(e.pollMenuButton);
}
@ -29,7 +28,7 @@ class Polling extends MultiUsers {
async quickPoll() {
await waitAndClearDefaultPresentationNotification(this.modPage);
await utilPresentation.uploadPresentation(this.modPage, e.questionSlideFileName);
await utilPresentation.uploadSinglePresentation(this.modPage, e.questionSlideFileName);
await this.modPage.waitAndClick(e.quickPoll);
await this.modPage.waitForSelector(e.pollMenuButton);
@ -87,7 +86,7 @@ class Polling extends MultiUsers {
await waitAndClearDefaultPresentationNotification(this.modPage);
await util.startPoll(this.modPage);
await utilPresentation.uploadPresentation(this.modPage, e.questionSlideFileName);
await utilPresentation.uploadSinglePresentation(this.modPage, e.questionSlideFileName);
await this.modPage.waitAndClick(e.publishPollingLabel);
// Check poll results

View File

@ -5,16 +5,18 @@ test.describe.parallel('Polling', () => {
test.describe.parallel('Manage', () => {
test('Create Poll @ci', async ({ browser, context, page }) => {
const polling = new Polling(browser, context);
await polling.initPages(page);
await polling.initPages(page, true);
await polling.createPoll();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#start-an-anonymous-poll-automated
test('Create anonymous poll @ci', async ({ browser, context, page }) => {
const polling = new Polling(browser, context);
await polling.initPages(page);
await polling.pollAnonymous();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#quick-poll-option-automated
test('Create quick poll - from the slide', async ({ browser, context, page }) => {
const polling = new Polling(browser, context);
await polling.initPages(page);

View File

@ -2,11 +2,10 @@ const { expect, default: test } = require('@playwright/test');
const { MultiUsers } = require('../user/multiusers');
const Page = require('../core/page');
const e = require('../core/elements');
const { checkSvgIndex, getSlideOuterHtml, uploadPresentation } = require('./util.js');
const { checkSvgIndex, getSlideOuterHtml, uploadSinglePresentation, uploadMultiplePresentations } = require('./util.js');
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const { sleep } = require('../core/helpers');
const { getSettings } = require('../core/settings');
const { waitAndClearDefaultPresentationNotification } = require('../notifications/util');
class Presentation extends MultiUsers {
constructor(browser, context) {
@ -61,15 +60,14 @@ class Presentation extends MultiUsers {
await userFrame.hasElement('video');
}
async uploadPresentationTest() {
await waitAndClearDefaultPresentationNotification(this.modPage);
async uploadSinglePresentationTest() {
await this.modPage.waitForSelector(e.skipSlide);
const modSlides0 = await getSlideOuterHtml(this.modPage);
const userSlides0 = await getSlideOuterHtml(this.userPage);
await expect(modSlides0).toEqual(userSlides0);
await uploadPresentation(this.modPage, e.uploadPresentationFileName);
await uploadSinglePresentation(this.modPage, e.uploadPresentationFileName);
const modSlides1 = await getSlideOuterHtml(this.modPage);
const userSlides1 = await getSlideOuterHtml(this.userPage);
@ -79,6 +77,34 @@ class Presentation extends MultiUsers {
await expect(userSlides0).not.toEqual(userSlides1);
}
async uploadMultiplePresentationsTest() {
await this.modPage.waitForSelector(e.skipSlide);
const modSlides0 = await this.modPage.page.evaluate(getSvgOuterHtml);
const userSlides0 = await this.userPage.page.evaluate(getSvgOuterHtml);
await expect(modSlides0).toEqual(userSlides0);
await uploadMultiplePresentations(this.modPage, [e.uploadPresentationFileName, e.questionSlideFileName]);
const modSlides1 = await this.userPage.page.evaluate(async () => document.querySelector('svg g g g').outerHTML);
const userSlides1 = await this.modPage.page.evaluate(async () => document.querySelector('svg g g g').outerHTML);
await expect(modSlides1).toEqual(userSlides1);
await expect(modSlides0).not.toEqual(modSlides1);
await expect(userSlides0).not.toEqual(userSlides1);
}
async fitToWidthTest() {
await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME);
await this.modPage.waitForSelector(e.skipSlide);
await this.modPage.waitAndClick(e.userListToggleBtn);
await uploadSinglePresentation(this.modPage, e.uploadPresentationFileName);
const width1 = await this.modPage.page.locator(e.whiteboard).getAttribute("width");
await this.modPage.waitAndClick(e.fitToWidthButton);
const width2 = await this.modPage.page.locator(e.whiteboard).getAttribute("width");
await expect(Number(width2) > Number(width1)).toBeTruthy();
}
async allowAndDisallowDownload(testInfo) {
const { presentationDownloadable } = getSettings();
test.fail(!presentationDownloadable, 'Presentation download is disable');

View File

@ -20,11 +20,25 @@ test.describe.parallel('Presentation', () => {
await presentation.startExternalVideo();
});
test('Presentation fit to width', async ({ browser, context, page }) => {
const presentation = new Presentation(browser, context);
await presentation.initPages(page);
await presentation.fitToWidthTest();
});
test.describe.parallel('Manage', () => {
test('Upload presentation @ci', async ({ browser, context, page }) => {
// https://docs.bigbluebutton.org/2.5/release-tests.html#uploading-a-presentation-automated
test('Upload single presentation @ci', async ({ browser, context, page }) => {
const presentation = new Presentation(browser, context);
await presentation.initPages(page);
await presentation.uploadPresentationTest();
await presentation.initPages(page, true);
await presentation.uploadSinglePresentationTest();
});
// https://docs.bigbluebutton.org/2.5/release-tests.html#uploading-multiple-presentations-automated
test('Upload multiple presentations', async ({ browser, context, page }) => {
const presentation = new Presentation(browser, context);
await presentation.initPages(page, true);
await presentation.uploadMultiplePresentationsTest();
});
test.skip('Allow and disallow presentation download @ci', async ({ browser, context, page }, testInfo) => {

View File

@ -16,7 +16,7 @@ async function getSlideOuterHtml(testPage) {
}, [e.currentSlideImg]);
}
async function uploadPresentation(test, fileName, uploadTimeout = ELEMENT_WAIT_LONGER_TIME) {
async function uploadSinglePresentation(test, fileName, uploadTimeout = ELEMENT_WAIT_LONGER_TIME) {
await test.waitAndClick(e.actions);
await test.waitAndClick(e.managePresentations);
await test.waitForSelector(e.fileUpload);
@ -29,6 +29,20 @@ async function uploadPresentation(test, fileName, uploadTimeout = ELEMENT_WAIT_L
await test.hasText(e.smallToastMsg, e.presentationUploadedToast, uploadTimeout);
}
async function uploadMultiplePresentations(test, fileNames, uploadTimeout = ELEMENT_WAIT_LONGER_TIME) {
await test.waitAndClick(e.actions);
await test.waitAndClick(e.managePresentations);
await test.waitForSelector(e.fileUpload);
await test.page.setInputFiles(e.fileUpload, fileNames.map(function(fileName) { return path.join(__dirname, `../core/media/${fileName}`); }));
await test.hasText('body', e.statingUploadPresentationToast);
await test.waitAndClick(e.confirmManagePresentation);
await test.hasText(e.presentationStatusInfo, [e.convertingPresentationFileToast], uploadTimeout);
await test.hasText(e.smallToastMsg, e.presentationUploadedToast, uploadTimeout);
}
exports.checkSvgIndex = checkSvgIndex;
exports.getSlideOuterHtml = getSlideOuterHtml;
exports.uploadPresentation = uploadPresentation;
exports.uploadSinglePresentation = uploadSinglePresentation;
exports.uploadMultiplePresentations = uploadMultiplePresentations;

View File

@ -13,8 +13,11 @@ class MultiUsers {
this.context = context;
}
async initPages(page1) {
async initPages(page1, waitAndClearDefaultPresentationNotificationModPage = false) {
await this.initModPage(page1);
if (waitAndClearDefaultPresentationNotificationModPage) {
await waitAndClearDefaultPresentationNotification(this.modPage);
}
await this.initUserPage();
}

View File

@ -9,9 +9,10 @@ const iPhone11 = devices['iPhone 11'];
test.describe.parallel('User', () => {
test.describe.parallel('Actions', () => {
// https://docs.bigbluebutton.org/2.5/release-tests.html#set-status--raise-hand-automated
test('Raise and lower Hand Toast', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page);
await multiusers.initModPage(page, true);
await multiusers.raiseAndLowerHand();
});
@ -23,6 +24,7 @@ test.describe.parallel('User', () => {
});
test.describe.parallel('List', () => {
// https://docs.bigbluebutton.org/2.5/release-tests.html#set-status--raise-hand-automated
test('Change user status @ci', async ({ browser, page }) => {
const status = new Status(browser, page);
await status.init(true, true);

View File

@ -2,6 +2,7 @@ const { test } = require('@playwright/test');
const { Webcam } = require('./webcam');
test.describe.parallel('Webcam @ci', () => {
// https://docs.bigbluebutton.org/2.5/release-tests.html#joining-webcam-automated
test('Shares webcam', async ({ browser, page }) => {
const webcam = new Webcam(browser, page);
await webcam.init(true, true);

View File

@ -2,6 +2,21 @@
set +x
removeOldOverride() {
service_name=$1
# check if override file has been modified. If not it can be safely removed
if [ -f "/etc/systemd/system/${service_name}.service.d/override.conf" ] ; then
if echo "d32a00b9a2669b3fe757b8de3470e358 /etc/systemd/system/${service_name}.service.d/override.conf" | md5sum -c --quiet 2>/dev/null >/dev/null ; then
rm -f "/etc/systemd/system/${service_name}.service.d/override.conf"
fi
fi
if [ -d "/etc/systemd/system/${service_name}.service.d" ]; then
if [ $(ls "/etc/systemd/system/${service_name}.service.d" |wc -l) = 0 ]; then
rmdir "/etc/systemd/system/${service_name}.service.d"
fi
fi
}
BIGBLUEBUTTON_USER=bigbluebutton
if ! id freeswitch >/dev/null 2>&1; then
@ -137,5 +152,12 @@ fi
# Fix permissions for logging
chown bigbluebutton:bigbluebutton /var/log/bbb-fsesl-akka
# cleanup old overrides
removeOldOverride bbb-apps-akka
removeOldOverride bbb-fsesl-akka
removeOldOverride bbb-transcode-akka
# Load the overrides
systemctl daemon-reload

View File

@ -1,7 +1,7 @@
[Unit]
Description=Etherpad Server
Wants=redis-server.service
After=syslog.target network.target
After=syslog.target network.target redis-server.service
PartOf=bigbluebutton.target
[Service]

View File

@ -1,7 +1,7 @@
[Unit]
Description=BigBlueButton HTML5 service
Wants=redis.service mongod.service disable-transparent-huge-pages.service bbb-pads.service
After=redis.service mongod.service disable-transparent-huge-pages.service bbb-pads.service syslog.target network.target
Wants=redis-server.service mongod.service disable-transparent-huge-pages.service bbb-pads.service
After=redis-server.service mongod.service disable-transparent-huge-pages.service bbb-pads.service syslog.target network.target
PartOf=bigbluebutton.target
[Service]

View File

@ -1,7 +1,7 @@
[Unit]
Description=BigBlueButton Pads
Wants=redis.service etherpad.service
After=syslog.target network.target
Wants=redis-server.service etherpad.service
After=syslog.target network.target redis-server.service etherpad.service
PartOf=bigbluebutton.target
[Service]

View File

@ -1,7 +1,8 @@
[Unit]
Description=BigBlueButton Web Application
Requires=network.target
After=redis.service
Wants=redis-server.service
After=redis-server.service
PartOf=bigbluebutton.target
[Service]

View File

@ -1,7 +1,7 @@
[Unit]
Description=BigBlueButton Webhooks
Wants=redis-server.service
After=syslog.target network.target
After=syslog.target network.target redis-server.service
PartOf=bigbluebutton.target
[Service]

View File

@ -1,7 +1,7 @@
[Unit]
Description=BigBlueButton WebRTC SFU
Wants=redis-server.service
After=syslog.target network.target freeswitch.service kurento-media-server.service
After=syslog.target network.target freeswitch.service kurento-media-server.service redis-server.service
PartOf=bigbluebutton.target
[Service]

View File

@ -37,6 +37,6 @@ gem 'bbbevents', '~> 1.2'
gem 'rake', '>= 12.3', '<14'
group :test, optional: true do
gem 'rubocop', '~> 0.79.0'
gem 'rubocop', '~> 1.31.1'
gem 'minitest', '~> 5.14.1'
end

View File

@ -17,11 +17,11 @@ GEM
ffi (1.15.5)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
jaro_winkler (1.5.4)
java_properties (0.0.4)
journald-logger (3.1.0)
journald-native (~> 1.0)
journald-native (1.0.12)
json (2.6.2)
jwt (2.3.0)
locale (2.1.3)
loofah (2.18.0)
@ -52,19 +52,26 @@ GEM
redis (4.6.0)
redis-namespace (1.8.2)
redis (>= 3.0.4)
regexp_parser (2.5.0)
resque (2.0.0)
mono_logger (~> 1.0)
multi_json (~> 1.0)
redis-namespace (~> 1.6)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
rubocop (0.79.0)
jaro_winkler (~> 1.5.1)
rexml (3.2.5)
rubocop (1.31.1)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 2.7.0.1)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.18.0)
parser (>= 3.1.1.0)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@ -77,7 +84,7 @@ GEM
tilt (2.0.10)
tzinfo (1.2.9)
thread_safe (~> 0.1)
unicode-display_width (1.6.1)
unicode-display_width (2.2.0)
vegas (0.1.11)
rack (>= 1.0.0)
@ -102,7 +109,7 @@ DEPENDENCIES
rb-inotify (~> 0.10)
redis (~> 4.1)
resque (~> 2.0.0)
rubocop (~> 0.79.0)
rubocop (~> 1.31.1)
rubyzip (~> 2.0)
BUNDLED WITH

View File

@ -51,7 +51,7 @@ module BigBlueButton
temp_out = "#{File.dirname(png_out)}/temp-#{File.basename(png_out, '.png')}"
status = BigBlueButton.execute(
[
'pdftocairo', '-png', '-f', page_num.to_s, '-l', page_num.to_s, '-scale-to', scale.to_s, '-singlefile',
'pdftocairo', '-png', '-f', page_num.to_s, '-l', page_num.to_s, '-scale-to', scale.to_s, '-singlefile', '-cropbox',
pdf_presentation, temp_out,
],
false

View File

@ -1,7 +1,7 @@
[Unit]
Description=BigBlueButton resque worker for recordings
Wants=redis.service
After=redis.service
Wants=redis-server.service
After=redis-server.service
PartOf=bigbluebutton.target
[Service]

View File

@ -1,5 +1,7 @@
[Unit]
Description=BigBlueButton recording processing starter
Wants=redis-server.service
After=redis-server.service
PartOf=bigbluebutton.target
[Service]