Merge pull request #15868 from danielpetri1/capture-notes-toast

This commit is contained in:
Gustavo Trott 2022-11-10 21:54:48 -03:00 committed by GitHub
commit b837d95d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 54 additions and 29 deletions

View File

@ -186,7 +186,7 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(PresentationPageConversionStartedEventMsg.NAME, routing)
val header = BbbClientMsgHeader(CaptureSharedNotesReqEvtMsg.NAME, meetingId, "not-used")
val body = CaptureSharedNotesReqEvtMsgBody(m.parentMeetingId, m.meetingName, m.sequence)
val body = CaptureSharedNotesReqEvtMsgBody(m.parentMeetingId, m.meetingName)
val event = CaptureSharedNotesReqEvtMsg(header, body)
bus.outGW.send(BbbCommonEnvCoreMsg(envelope, event))
@ -195,14 +195,14 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
def handle(m: PadCapturePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val userId: String = "system"
val jobId: String = RandomStringGenerator.randomAlphanumericString(16);
val jobId: String = s"${m.body.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val jobType = "PadCaptureJob"
val filename = s"${m.body.meetingName}-notes"
val filename = m.body.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename))
val exportJob = new ExportJob(jobId, jobType, filename, m.body.padId, "", true, List(m.body.sequence), m.body.parentMeetingId, presentationUploadToken)
val exportJob = new ExportJob(jobId, jobType, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
bus.outGW.send(job)

View File

@ -117,7 +117,7 @@ case class PadUpdateCmdMsgBody(groupId: String, name: String, text: String)
// pads -> apps
object PadCapturePubMsg { val NAME = "PadCapturePubMsg" }
case class PadCapturePubMsg(header: BbbCoreHeaderWithMeetingId, body: PadCapturePubMsgBody) extends PadStandardMsg
case class PadCapturePubMsgBody(parentMeetingId: String, breakoutId: String, padId: String, meetingName: String, sequence: Int)
case class PadCapturePubMsgBody(parentMeetingId: String, breakoutId: String, padId: String, filename: String)
// client -> apps
object PadPinnedReqMsg { val NAME = "PadPinnedReqMsg" }

View File

@ -39,6 +39,6 @@ case class NewPresAnnFileAvailableEvtMsgBody(fileURI: String, presId: String)
object CaptureSharedNotesReqEvtMsg { val NAME = "CaptureSharedNotesReqEvtMsg" }
case class CaptureSharedNotesReqEvtMsg(header: BbbClientMsgHeader, body: CaptureSharedNotesReqEvtMsgBody) extends BbbCoreMsg
case class CaptureSharedNotesReqEvtMsgBody(parentMeetingId: String, meetingName: String, sequence: Int)
case class CaptureSharedNotesReqEvtMsgBody(parentMeetingId: String, meetingName: String)
// ------------ akka-apps to client ------------

View File

@ -10,9 +10,6 @@
"imagemagick": "/usr/bin/convert",
"pdftocairo": "/usr/bin/pdftocairo"
},
"captureNotes": {
"timeout": 5000
},
"collector": {
"pngWidthRasterizedSlides": 2560
},

View File

@ -117,7 +117,7 @@ async function sleep(ms) {
/** Export shared notes via bbb-pads in the desired format
* @param {Integer} retries - Number of retries to get the shared notes
*/
async function collectSharedNotes(retries) {
async function collectSharedNotes(retries = 3) {
/** One of the following formats is supported:
etherpad / html / pdf / txt / doc / odf */
@ -128,12 +128,6 @@ async function collectSharedNotes(retries) {
const notes_endpoint = `${config.bbbPadsAPI}/p/${padId}/export/${notesFormat}`;
const filePath = path.join(dropbox, filename);
const [sequence] = JSON.parse(exportJob.pages);
const timeout = (sequence - 1) * config.captureNotes.timeout;
// Wait for the bbb-pads API to be available
await sleep(timeout);
const finishedDownload = promisify(stream.finished);
const writer = fs.createWriteStream(filePath);
@ -142,13 +136,15 @@ async function collectSharedNotes(retries) {
method: 'GET',
url: notes_endpoint,
responseType: 'stream',
timeout: timeout,
});
response.data.pipe(writer);
await finishedDownload(writer);
} catch (err) {
if (retries > 0) {
logger.info(`Retrying ${jobId} in ${timeout}ms...`);
if (retries > 0 && err?.response?.status == 429) {
// Wait for the bbb-pads API to be available due to rate limiting
const backoff = err.response.headers['retry-after'] * 1000;
logger.info(`Retrying ${jobId} in ${backoff}ms...`);
await sleep(backoff);
return collectSharedNotes(retries - 1);
} else {
logger.error(`Could not download notes in job ${jobId}`);
@ -162,6 +158,6 @@ async function collectSharedNotes(retries) {
switch (exportJob.jobType) {
case 'PresentationWithAnnotationExportJob': return collectAnnotationsFromRedis();
case 'PresentationWithAnnotationDownloadJob': return collectAnnotationsFromRedis();
case 'PadCaptureJob': return collectSharedNotes(3);
case 'PadCaptureJob': return collectSharedNotes();
default: return logger.error(`Unknown job type ${exportJob.jobType}`);
}

View File

@ -76,7 +76,7 @@ async function upload(filePath) {
{headers: formData.getHeaders()});
logger.info(`Upload of job ${exportJob.jobId} returned ${res.data}`);
} catch (error) {
return logger.error(`Could upload job ${exportJob.jobId}: ${error}`);
return logger.error(`Could not upload job ${exportJob.jobId}: ${error}`);
}
}

View File

@ -32,6 +32,7 @@ function breakouts() {
sequence: 1,
shortName: 1,
timeRemaining: 1,
captureNotes: 1,
},
};

View File

@ -5,11 +5,10 @@ export default function captureSharedNotes({ body }, meetingId) {
check(body, Object);
check(meetingId, String);
const { parentMeetingId, meetingName, sequence } = body;
const { parentMeetingId, meetingName } = body;
check(parentMeetingId, String);
check(meetingName, String);
check(sequence, Number);
padCapture(meetingId, parentMeetingId, meetingName, sequence);
padCapture(meetingId, parentMeetingId, meetingName);
}

View File

@ -3,7 +3,7 @@ import Pads from '/imports/api/pads';
import RedisPubSub from '/imports/startup/server/redis';
import Logger from '/imports/startup/server/logger';
export default function padCapture(meetingId, parentMeetingId, meetingName, sequence) {
export default function padCapture(meetingId, parentMeetingId, meetingName) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'PadCapturePubMsg';
@ -12,7 +12,6 @@ export default function padCapture(meetingId, parentMeetingId, meetingName, sequ
check(meetingId, String);
check(parentMeetingId, String);
check(meetingName, String);
check(sequence, Number);
const pad = Pads.findOne(
{
@ -26,12 +25,12 @@ export default function padCapture(meetingId, parentMeetingId, meetingName, sequ
},
);
const filename = `${meetingName}-notes`;
const payload = {
parentMeetingId,
breakoutId: meetingId,
padId: pad.padId,
meetingName,
sequence,
filename,
};
Logger.info(`Sending PadCapturePubMsg for meetingId=${meetingId} parentMeetingId=${parentMeetingId} padId=${pad.padId}`);

View File

@ -5,6 +5,7 @@ import injectNotify from '/imports/ui/components/common/toast/inject-notify/comp
import humanizeSeconds from '/imports/utils/humanizeSeconds';
import _ from 'lodash';
import BreakoutRemainingTimeComponent from './component';
import BreakoutService from '/imports/ui/components/breakout-room/service';
import { Text, Time } from './styles';
const intlMessages = defineMessages({
@ -146,6 +147,7 @@ export default injectNotify(injectIntl(withTracker(({
if (fromBreakoutPanel) data.bold = true;
} else {
clearInterval(timeRemainingInterval);
BreakoutService.setCapturedNotesUploading();
data.message = intl.formatMessage(timeEndedMessage || intlMessages.breakoutWillClose);
}
} else if (breakoutRoom) {

View File

@ -6,6 +6,8 @@ import Users from '/imports/api/users';
import UserListService from '/imports/ui/components/user-list/service';
import fp from 'lodash/fp';
import UsersPersistentData from '/imports/api/users-persistent-data';
import { UploadingPresentations } from '/imports/api/presentations';
import _ from 'lodash';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
@ -36,7 +38,35 @@ const getBreakoutRoomUrl = (breakoutId) => {
return breakoutUrlData;
};
const setCapturedNotesUploading = () => {
const breakoutRooms = findBreakouts();
breakoutRooms.forEach((breakout) => {
if (breakout.captureNotes) {
const filename = breakout.shortName;
const temporaryPresentationId = `${breakout.breakoutId}-notes`;
UploadingPresentations.upsert({
temporaryPresentationId,
}, {
$set: {
id: _.uniqueId(filename),
temporaryPresentationId,
progress: 0,
filename,
lastModifiedUploader: false,
upload: {
done: false,
error: false,
},
uploadTimestamp: new Date(),
},
});
}
});
};
const endAllBreakouts = () => {
setCapturedNotesUploading();
makeCall('endAllBreakouts');
};
@ -214,4 +244,5 @@ export default {
sortUsersByName: UserListService.sortUsersByName,
isUserInBreakoutRoom,
checkInviteModerators,
setCapturedNotesUploading,
};

View File

@ -539,7 +539,7 @@
"windowMs": 90000,
// maximum number of requests per IP to allow during the rate limit window
"max": 10
"max": 16
},
/*