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:

committed by
Chromium LUCI CQ

parent
f62c540a9f
commit
ed016cd505
ash/webui/boca_ui
chrome/test/data/webui/chromeos/boca_ui
chromeos/ash/components/boca/session_api
@@ -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());
|
||||||
|
Reference in New Issue
Block a user