|
|
|
|
@ -1,48 +1,40 @@
|
|
|
|
|
part of "client_extension.dart";
|
|
|
|
|
|
|
|
|
|
extension AnalyticsClientExtension on Client {
|
|
|
|
|
// get analytics room matching targetlanguage
|
|
|
|
|
// if not present, create it and invite teachers of that language
|
|
|
|
|
// set description to let people know what the hell it is
|
|
|
|
|
/// Get the logged in user's analytics room matching
|
|
|
|
|
/// a given langCode. If not present, create it.
|
|
|
|
|
Future<Room> _getMyAnalyticsRoom(String langCode) async {
|
|
|
|
|
await roomsLoading;
|
|
|
|
|
// ensure room state events (room create,
|
|
|
|
|
// to check for analytics type) are loaded
|
|
|
|
|
for (final room in rooms) {
|
|
|
|
|
if (room.partial) await room.postLoad();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final Room? analyticsRoom = analyticsRoomLocal(langCode);
|
|
|
|
|
|
|
|
|
|
final Room? analyticsRoom = _analyticsRoomLocal(langCode);
|
|
|
|
|
if (analyticsRoom != null) return analyticsRoom;
|
|
|
|
|
|
|
|
|
|
return _makeAnalyticsRoom(langCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//note: if langCode is null and user has >1 analyticsRooms then this could
|
|
|
|
|
//return the wrong one. this is to account for when an exchange might not
|
|
|
|
|
//be in a class.
|
|
|
|
|
Room? _analyticsRoomLocal(String? langCode, [String? userIdParam]) {
|
|
|
|
|
/// Get local analytics room for a given langCode and
|
|
|
|
|
/// optional userId (if not specified, uses current user).
|
|
|
|
|
/// If user is invited to the room, joins the room.
|
|
|
|
|
Room? _analyticsRoomLocal(String langCode, [String? userIdParam]) {
|
|
|
|
|
final Room? analyticsRoom = rooms.firstWhereOrNull((e) {
|
|
|
|
|
return e.isAnalyticsRoom &&
|
|
|
|
|
e.isAnalyticsRoomOfUser(userIdParam ?? userID!) &&
|
|
|
|
|
(langCode != null ? e.isMadeForLang(langCode) : true);
|
|
|
|
|
e.isMadeForLang(langCode);
|
|
|
|
|
});
|
|
|
|
|
if (analyticsRoom != null &&
|
|
|
|
|
analyticsRoom.membership == Membership.invite) {
|
|
|
|
|
debugger(when: kDebugMode);
|
|
|
|
|
analyticsRoom
|
|
|
|
|
.join()
|
|
|
|
|
.onError(
|
|
|
|
|
analyticsRoom.join().onError(
|
|
|
|
|
(error, stackTrace) =>
|
|
|
|
|
ErrorHandler.logError(e: error, s: stackTrace),
|
|
|
|
|
)
|
|
|
|
|
.then((value) => analyticsRoom.postLoad());
|
|
|
|
|
);
|
|
|
|
|
return analyticsRoom;
|
|
|
|
|
}
|
|
|
|
|
return analyticsRoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates an analytics room with the specified language code and returns the created room.
|
|
|
|
|
/// Additionally, the room is added to the user's spaces and all teachers are invited to the room.
|
|
|
|
|
///
|
|
|
|
|
/// If the room does not appear immediately after creation, this method waits for it to appear in sync.
|
|
|
|
|
/// Returns the created [Room] object.
|
|
|
|
|
Future<Room> _makeAnalyticsRoom(String langCode) async {
|
|
|
|
|
final String roomID = await createRoom(
|
|
|
|
|
creationContent: {
|
|
|
|
|
@ -53,7 +45,6 @@ extension AnalyticsClientExtension on Client {
|
|
|
|
|
topic: "This room stores learning analytics for $userID.",
|
|
|
|
|
invite: [
|
|
|
|
|
...(await myTeachers).map((e) => e.id),
|
|
|
|
|
// BotName.localBot,
|
|
|
|
|
BotName.byEnvironment,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
@ -66,14 +57,14 @@ extension AnalyticsClientExtension on Client {
|
|
|
|
|
|
|
|
|
|
// add this analytics room to all spaces so teachers can join them
|
|
|
|
|
// via the space hierarchy
|
|
|
|
|
await analyticsRoom?.addAnalyticsRoomToSpaces();
|
|
|
|
|
analyticsRoom?.addAnalyticsRoomToSpaces();
|
|
|
|
|
|
|
|
|
|
// and invite all teachers to new analytics room
|
|
|
|
|
await analyticsRoom?.inviteTeachersToAnalyticsRoom();
|
|
|
|
|
analyticsRoom?.inviteTeachersToAnalyticsRoom();
|
|
|
|
|
return getRoomById(roomID)!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all my analytics rooms
|
|
|
|
|
/// Get all my analytics rooms
|
|
|
|
|
List<Room> get _allMyAnalyticsRooms => rooms
|
|
|
|
|
.where(
|
|
|
|
|
(e) => e.isAnalyticsRoomOfUser(userID!),
|
|
|
|
|
@ -83,76 +74,77 @@ extension AnalyticsClientExtension on Client {
|
|
|
|
|
// migration function to change analytics rooms' vsibility to public
|
|
|
|
|
// so they will appear in the space hierarchy
|
|
|
|
|
Future<void> _updateAnalyticsRoomVisibility() async {
|
|
|
|
|
final List<Future> makePublicFutures = [];
|
|
|
|
|
for (final Room room in allMyAnalyticsRooms) {
|
|
|
|
|
final visability = await getRoomVisibilityOnDirectory(room.id);
|
|
|
|
|
if (visability != Visibility.public) {
|
|
|
|
|
await setRoomVisibilityOnDirectory(
|
|
|
|
|
room.id,
|
|
|
|
|
visibility: Visibility.public,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await Future.wait(makePublicFutures);
|
|
|
|
|
await Future.wait(
|
|
|
|
|
allMyAnalyticsRooms.map((room) async {
|
|
|
|
|
final visability = await getRoomVisibilityOnDirectory(room.id);
|
|
|
|
|
if (visability != Visibility.public) {
|
|
|
|
|
await setRoomVisibilityOnDirectory(
|
|
|
|
|
room.id,
|
|
|
|
|
visibility: Visibility.public,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add all the users' analytics room to all the spaces the student studies in
|
|
|
|
|
// So teachers can join them via space hierarchy
|
|
|
|
|
// Will not always work, as there may be spaces where students don't have permission to add chats
|
|
|
|
|
// But allows teachers to join analytics rooms without being invited
|
|
|
|
|
Future<void> _addAnalyticsRoomsToAllSpaces() async {
|
|
|
|
|
final List<Future> addFutures = [];
|
|
|
|
|
/// Add all the users' analytics room to all the spaces the user is studying in
|
|
|
|
|
/// so teachers can join them via space hierarchy.
|
|
|
|
|
/// Allows teachers to join analytics rooms without being invited.
|
|
|
|
|
void _addAnalyticsRoomsToAllSpaces() {
|
|
|
|
|
for (final Room room in allMyAnalyticsRooms) {
|
|
|
|
|
addFutures.add(room.addAnalyticsRoomToSpaces());
|
|
|
|
|
room.addAnalyticsRoomToSpaces();
|
|
|
|
|
}
|
|
|
|
|
await Future.wait(addFutures);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Invite teachers to all my analytics room
|
|
|
|
|
// Handles case when students cannot add analytics room to space(s)
|
|
|
|
|
// So teacher is still able to get analytics data for this student
|
|
|
|
|
Future<void> _inviteAllTeachersToAllAnalyticsRooms() async {
|
|
|
|
|
final List<Future> inviteFutures = [];
|
|
|
|
|
for (final Room analyticsRoom in allMyAnalyticsRooms) {
|
|
|
|
|
inviteFutures.add(analyticsRoom.inviteTeachersToAnalyticsRoom());
|
|
|
|
|
/// Invite teachers to all my analytics room.
|
|
|
|
|
/// Handles case when students cannot add analytics room to space(s)
|
|
|
|
|
/// so teacher is still able to get analytics data for this student
|
|
|
|
|
void _inviteAllTeachersToAllAnalyticsRooms() {
|
|
|
|
|
for (final Room room in allMyAnalyticsRooms) {
|
|
|
|
|
room.inviteTeachersToAnalyticsRoom();
|
|
|
|
|
}
|
|
|
|
|
await Future.wait(inviteFutures);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Join all analytics rooms in all spaces
|
|
|
|
|
// Allows teachers to join analytics rooms without being invited
|
|
|
|
|
Future<void> _joinAnalyticsRoomsInAllSpaces() async {
|
|
|
|
|
final List<Future> joinFutures = [];
|
|
|
|
|
for (final Room space in (await _spacesImTeaching)) {
|
|
|
|
|
joinFutures.add(space.joinAnalyticsRoomsInSpace());
|
|
|
|
|
for (final Room space in _spacesImTeaching) {
|
|
|
|
|
// Each call to joinAnalyticsRoomsInSpace calls getSpaceHierarchy, which has a
|
|
|
|
|
// strict rate limit. So we wait a second between each call to prevent a 429 error.
|
|
|
|
|
await Future.delayed(
|
|
|
|
|
const Duration(seconds: 1),
|
|
|
|
|
() => space.joinAnalyticsRoomsInSpace(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
await Future.wait(joinFutures);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Join invited analytics rooms
|
|
|
|
|
// Checks for invites to any student analytics rooms
|
|
|
|
|
// Handles case of analytics rooms that can't be added to some space(s)
|
|
|
|
|
Future<void> _joinInvitedAnalyticsRooms() async {
|
|
|
|
|
final List<Room> allRooms = List.from(rooms);
|
|
|
|
|
for (final Room room in allRooms) {
|
|
|
|
|
if (room.membership == Membership.invite && room.isAnalyticsRoom) {
|
|
|
|
|
try {
|
|
|
|
|
await room.join();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
debugPrint("Failed to join analytics room ${room.id}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// Join invited analytics rooms.
|
|
|
|
|
/// Checks for invites to any student analytics rooms.
|
|
|
|
|
/// Handles case of analytics rooms that can't be added to some space(s).
|
|
|
|
|
void _joinInvitedAnalyticsRooms() {
|
|
|
|
|
Future.wait(
|
|
|
|
|
rooms
|
|
|
|
|
.where(
|
|
|
|
|
(room) =>
|
|
|
|
|
room.membership == Membership.invite && room.isAnalyticsRoom,
|
|
|
|
|
)
|
|
|
|
|
.map(
|
|
|
|
|
(room) => room.join().catchError((err, s) {
|
|
|
|
|
ErrorHandler.logError(e: err, s: s);
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// helper function to join all relevant analytics rooms
|
|
|
|
|
// and set up those rooms to be joined by relevant teachers
|
|
|
|
|
Future<void> _migrateAnalyticsRooms() async {
|
|
|
|
|
await _updateAnalyticsRoomVisibility();
|
|
|
|
|
await _addAnalyticsRoomsToAllSpaces();
|
|
|
|
|
await _inviteAllTeachersToAllAnalyticsRooms();
|
|
|
|
|
await _joinInvitedAnalyticsRooms();
|
|
|
|
|
await _joinAnalyticsRoomsInAllSpaces();
|
|
|
|
|
/// Helper function to join all relevant analytics rooms
|
|
|
|
|
/// and set up those rooms to be joined by other users.
|
|
|
|
|
void _migrateAnalyticsRooms() {
|
|
|
|
|
_updateAnalyticsRoomVisibility().then((_) {
|
|
|
|
|
_addAnalyticsRoomsToAllSpaces();
|
|
|
|
|
_inviteAllTeachersToAllAnalyticsRooms();
|
|
|
|
|
_joinInvitedAnalyticsRooms();
|
|
|
|
|
_joinAnalyticsRoomsInAllSpaces();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Map<String, DateTime?>> _allAnalyticsRoomsLastUpdated() async {
|
|
|
|
|
|