0

boca: Add access code to be part of the session payload.

This allows display access code in teacher UI so that student can use
the code to join a class session.

UX mock: http://shortn/_BpugcftImn

BocaAppBrowserProducerTest.*

Test: unit tested. BocaAppPageHandlerTest.GetSessionWithFullInputTest
Bug: b:374372602
Change-Id: Ia61582b49364e69c36b6d0949819f424f60709c4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5938901
Reviewed-by: Benjamin Zielinski <bzielinski@google.com>
Commit-Queue: April Zhou <aprilzhou@google.com>
Reviewed-by: Alex Gough <ajgo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1372980}
This commit is contained in:
April Zhou
2024-10-23 22:08:22 +00:00
committed by Chromium LUCI CQ
parent f62c540a9f
commit ed016cd505
11 changed files with 76 additions and 8 deletions

@@ -118,12 +118,18 @@ mojom::ConfigPtr SessionConfigProtoToMojom(::boca::Session* session) {
seconds + seconds +
static_cast<double>(nanos) / base::Time::kNanosecondsPerSecond); static_cast<double>(nanos) / base::Time::kNanosecondsPerSecond);
} }
std::string access_code;
if (session->has_join_code()) {
access_code = session->join_code().code();
}
return mojom::Config::New( return mojom::Config::New(
// Nanos are not used throughout session lifecycle so it's // Nanos are not used throughout session lifecycle so it's
// safe to only parse seconds. // safe to only parse seconds.
base::Seconds(session->duration().seconds()), start_time, base::Seconds(session->duration().seconds()), start_time,
std::move(teacher), std::move(students), std::move(on_task_config), std::move(teacher), std::move(students), std::move(on_task_config),
std::move(caption_config)); std::move(caption_config), access_code);
} }
} // namespace } // namespace
@@ -411,6 +417,11 @@ void BocaAppHandler::OnConsumerActivityUpdated(
OnStudentActivityUpdated(std::move(result)); OnStudentActivityUpdated(std::move(result));
} }
void BocaAppHandler::OnSessionStarted(const std::string& session_id,
const ::boca::UserIdentity& producer) {
UpdateSessionConfig();
}
void BocaAppHandler::OnSessionEnded(const std::string& session_id) { void BocaAppHandler::OnSessionEnded(const std::string& session_id) {
OnSessionConfigUpdated( OnSessionConfigUpdated(
mojom::SessionResult::NewError(mojom::GetSessionError::kEmpty)); mojom::SessionResult::NewError(mojom::GetSessionError::kEmpty));

@@ -76,10 +76,9 @@ class BocaAppHandler : public mojom::PageHandler,
// BocaSessionManager::Observer // BocaSessionManager::Observer
void OnConsumerActivityUpdated( void OnConsumerActivityUpdated(
const std::map<std::string, ::boca::StudentStatus>& activities) override; const std::map<std::string, ::boca::StudentStatus>& activities) override;
// When session start, UI is already most update to date, do
// not handle the event
void OnSessionStarted(const std::string& session_id, void OnSessionStarted(const std::string& session_id,
const ::boca::UserIdentity& producer) override {} const ::boca::UserIdentity& producer) override;
void OnSessionEnded(const std::string& session_id) override; void OnSessionEnded(const std::string& session_id) override;
void OnBundleUpdated(const ::boca::Bundle& bundle) override; void OnBundleUpdated(const ::boca::Bundle& bundle) override;
void OnSessionCaptionConfigUpdated( void OnSessionCaptionConfigUpdated(

@@ -257,7 +257,7 @@ TEST_F(BocaAppPageHandlerTest, CreateSessionWithFullInput) {
const auto config = mojom::Config::New( const auto config = mojom::Config::New(
session_duration, std::nullopt, nullptr, std::move(students), session_duration, std::nullopt, nullptr, std::move(students),
GetCommonTestLockOnTaskConfig(), GetCommonCaptionConfig()); GetCommonTestLockOnTaskConfig(), GetCommonCaptionConfig(), "");
// Page handler callback. // Page handler callback.
base::test::TestFuture<base::expected<std::unique_ptr<::boca::Session>, base::test::TestFuture<base::expected<std::unique_ptr<::boca::Session>,
google_apis::ApiErrorCode>> google_apis::ApiErrorCode>>
@@ -391,7 +391,7 @@ TEST_F(BocaAppPageHandlerTest, CreateSessionWithCritialInputOnly) {
const auto config = mojom::Config::New( const auto config = mojom::Config::New(
session_duration, std::nullopt, nullptr, session_duration, std::nullopt, nullptr,
std::vector<mojom::IdentityPtr>{}, mojom::OnTaskConfigPtr(nullptr), std::vector<mojom::IdentityPtr>{}, mojom::OnTaskConfigPtr(nullptr),
mojom::CaptionConfigPtr(nullptr)); mojom::CaptionConfigPtr(nullptr), "");
::boca::UserIdentity teacher; ::boca::UserIdentity teacher;
teacher.set_gaia_id(kGaiaId); teacher.set_gaia_id(kGaiaId);
@@ -450,6 +450,9 @@ TEST_F(BocaAppPageHandlerTest, GetSessionWithFullInputTest) {
teacher->set_gaia_id("000"); teacher->set_gaia_id("000");
teacher->set_photo_url("cdn://s"); teacher->set_photo_url("cdn://s");
auto* access_code = session->mutable_join_code();
access_code->set_code("testCode");
auto* student_groups_1 = auto* student_groups_1 =
session->mutable_roster()->mutable_student_groups()->Add(); session->mutable_roster()->mutable_student_groups()->Add();
student_groups_1->set_title(kMainStudentGroupName); student_groups_1->set_title(kMainStudentGroupName);
@@ -495,6 +498,7 @@ TEST_F(BocaAppPageHandlerTest, GetSessionWithFullInputTest) {
EXPECT_EQ("000", result->teacher->id); EXPECT_EQ("000", result->teacher->id);
EXPECT_EQ("cdn://s", result->teacher->photo_url->spec()); EXPECT_EQ("cdn://s", result->teacher->photo_url->spec());
EXPECT_EQ("testCode", result->access_code);
EXPECT_EQ(true, result->caption_config->session_caption_enabled); EXPECT_EQ(true, result->caption_config->session_caption_enabled);
EXPECT_EQ(true, result->caption_config->session_translation_enabled); EXPECT_EQ(true, result->caption_config->session_translation_enabled);
@@ -1331,6 +1335,18 @@ TEST_F(BocaAppPageHandlerTest, RemoveStudentWithNonActiveSession) {
EXPECT_EQ(mojom::RemoveStudentError::kInvalid, future_1.Get().value()); EXPECT_EQ(mojom::RemoveStudentError::kInvalid, future_1.Get().value());
} }
TEST_F(BocaAppPageHandlerTest, OnSessionSessionStartedSucceed) {
auto session = GetCommonActiveSessionProto();
EXPECT_CALL(*session_manager(), GetCurrentSession())
.WillOnce(Return(&session));
base::test::TestFuture<mojom::SessionResultPtr> future;
boca_app_handler()->SetSessionConfigInterceptorCallbackForTesting(
future.GetCallback());
boca_app_handler()->OnSessionStarted(std::string(), ::boca::UserIdentity());
auto result = future.Take();
ASSERT_TRUE(result->is_config());
}
TEST_F(BocaAppPageHandlerTest, OnSessionEndedSucceed) { TEST_F(BocaAppPageHandlerTest, OnSessionEndedSucceed) {
base::test::TestFuture<mojom::SessionResultPtr> future; base::test::TestFuture<mojom::SessionResultPtr> future;
boca_app_handler()->SetSessionConfigInterceptorCallbackForTesting( boca_app_handler()->SetSessionConfigInterceptorCallbackForTesting(

@@ -60,6 +60,9 @@ struct Config{
array<Identity> students; array<Identity> students;
OnTaskConfig on_task_config; OnTaskConfig on_task_config;
CaptionConfig caption_config; CaptionConfig caption_config;
// Strings allow consumer to type and join a session. Will be alphabetic
// characters.
string? access_code;
}; };
struct CaptionConfig { struct CaptionConfig {

@@ -95,6 +95,7 @@ export declare interface SessionConfig {
teacher?: Identity; teacher?: Identity;
onTaskConfig: OnTaskConfig; onTaskConfig: OnTaskConfig;
captionConfig: CaptionConfig; captionConfig: CaptionConfig;
accessCode?: string;
} }
/** /**

@@ -66,6 +66,7 @@ export function getSessionConfigMojomToUI(session: Config|
}), }),
}, },
captionConfig: session.captionConfig, captionConfig: session.captionConfig,
accessCode: session.accessCode ? session.accessCode : ''
} }
}; };

@@ -111,6 +111,7 @@ class MockRemoteHandler extends PageHandlerRemote {
email: 'teacher@gmail.com', email: 'teacher@gmail.com',
photoUrl: {url: 'cdn0'}, photoUrl: {url: 'cdn0'},
}, },
accessCode: 'testCode',
students: [ students: [
{ {
id: '1', id: '1',
@@ -329,6 +330,7 @@ suite('ClientDelegateTest', function() {
{id: '1', name: 'cat', email: 'cat@gmail.com', photoUrl: 'cdn1'}, {id: '1', name: 'cat', email: 'cat@gmail.com', photoUrl: 'cdn1'},
{id: '2', name: 'dog', email: 'dog@gmail.com', photoUrl: 'cdn2'}, {id: '2', name: 'dog', email: 'dog@gmail.com', photoUrl: 'cdn2'},
], ],
accessCode: 'testCode',
onTaskConfig: { onTaskConfig: {
isLocked: true, isLocked: true,
tabs: [ tabs: [
@@ -379,6 +381,7 @@ suite('ClientDelegateTest', function() {
email: 'teacher@gmail.com', email: 'teacher@gmail.com',
photoUrl: {url: 'cdn0'}, photoUrl: {url: 'cdn0'},
}, },
accessCode: null,
captionConfig: { captionConfig: {
sessionCaptionEnabled: true, sessionCaptionEnabled: true,
localCaptionEnabled: true, localCaptionEnabled: true,
@@ -401,6 +404,7 @@ suite('ClientDelegateTest', function() {
isLocked: false, isLocked: false,
tabs: [], tabs: [],
}, },
accessCode: '',
captionConfig: { captionConfig: {
sessionCaptionEnabled: true, sessionCaptionEnabled: true,
localCaptionEnabled: true, localCaptionEnabled: true,

@@ -74,6 +74,9 @@ inline constexpr char kDeviceId[] = "deviceId";
inline constexpr char kActivity[] = "activity"; inline constexpr char kActivity[] = "activity";
inline constexpr char kUsers[] = "users"; inline constexpr char kUsers[] = "users";
inline constexpr char kTachyonGroupId[] = "tachyonGroupId"; inline constexpr char kTachyonGroupId[] = "tachyonGroupId";
inline constexpr char kJoinCode[] = "joinCode";
inline constexpr char kJoinCodeEnabled[] = "enabled";
inline constexpr char kCode[] = "code";
inline constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = inline constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("boca_classroom_integration", R"( net::DefineNetworkTrafficAnnotation("boca_classroom_integration", R"(

@@ -89,6 +89,20 @@ void ParseTeacherProtoFromJson(base::Value::Dict* session_dict,
} }
} }
void ParseJoinCodeProtoFromJson(base::Value::Dict* session_dict,
::boca::Session* session) {
if (!session_dict->FindDict(kJoinCode)) {
return;
}
auto* join_code = session->mutable_join_code();
join_code->set_enabled(session_dict->FindDict(kJoinCode)
->FindBool(kJoinCodeEnabled)
.value_or(false));
if (auto* ptr = session_dict->FindDict(kJoinCode)->FindString(kCode)) {
join_code->set_code(*ptr);
}
}
void ParseRosterProtoFromJson(base::Value::Dict* session_dict, void ParseRosterProtoFromJson(base::Value::Dict* session_dict,
::boca::Session* session) { ::boca::Session* session) {
auto* roster_dict = session_dict->FindDict(kRoster); auto* roster_dict = session_dict->FindDict(kRoster);
@@ -300,6 +314,8 @@ std::unique_ptr<::boca::Session> GetSessionProtoFromJson(std::string json,
ParseTeacherProtoFromJson(session_dict, session.get()); ParseTeacherProtoFromJson(session_dict, session.get());
ParseJoinCodeProtoFromJson(session_dict, session.get());
ParseRosterProtoFromJson(session_dict, session.get()); ParseRosterProtoFromJson(session_dict, session.get());
ParseSessionConfigProtoFromJson(session_dict, session.get(), is_producer); ParseSessionConfigProtoFromJson(session_dict, session.get(), is_producer);

@@ -24,6 +24,8 @@ namespace ash::boca {
// Proto to Json // Proto to Json
void ParseTeacherProtoFromJson(base::Value::Dict* session_dict, void ParseTeacherProtoFromJson(base::Value::Dict* session_dict,
::boca::Session* session); ::boca::Session* session);
void ParseJoinCodeProtoFromJson(base::Value::Dict* session_dict,
::boca::Session* session);
void ParseRosterProtoFromJson(base::Value::Dict* session_dict, void ParseRosterProtoFromJson(base::Value::Dict* session_dict,
::boca::Session* session); ::boca::Session* session);
void ParseSessionConfigProtoFromJson(base::Value::Dict* session_dict, void ParseSessionConfigProtoFromJson(base::Value::Dict* session_dict,

@@ -14,8 +14,9 @@
namespace ash::boca { namespace ash::boca {
namespace { namespace {
// Unit test cases for proto2json conversion is covered in // TODO(crbug.com/374364083):Refactor existing get session unit test.
// create_session_request_unittest.cc, not duplicating here. // Currently this file doesn't have full coverage for session_parser, the reset
// is covered in get_session_request_unittest, we should move those here.
constexpr char kFullSessionResponse[] = R"( constexpr char kFullSessionResponse[] = R"(
{ {
"startTime":{ "startTime":{
@@ -25,6 +26,10 @@ constexpr char kFullSessionResponse[] = R"(
"duration": { "duration": {
"seconds": 120 "seconds": 120
}, },
"joinCode":{
"enabled":true,
"code":"testCode"
},
"studentStatuses": { "studentStatuses": {
"2": { "2": {
"state": "ADDED" "state": "ADDED"
@@ -161,6 +166,13 @@ TEST_F(SessionParserTest, TestParseTeacherProtoFromJson) {
EXPECT_EQ("1", session_partial->teacher().gaia_id()); EXPECT_EQ("1", session_partial->teacher().gaia_id());
} }
TEST_F(SessionParserTest, TestParseJoinCodeProtoFromJson) {
ParseJoinCodeProtoFromJson(session_dict_full->GetIfDict(),
session_full.get());
EXPECT_TRUE(session_full->join_code().enabled());
EXPECT_EQ("testCode", session_full->join_code().code());
}
TEST_F(SessionParserTest, TestParseRosterProtoFromJson) { TEST_F(SessionParserTest, TestParseRosterProtoFromJson) {
ParseRosterProtoFromJson(session_dict_full->GetIfDict(), session_full.get()); ParseRosterProtoFromJson(session_dict_full->GetIfDict(), session_full.get());
ASSERT_EQ(2, session_full->roster().student_groups()[0].students().size()); ASSERT_EQ(2, session_full->roster().student_groups()[0].students().size());