From 07a6832a59b2517add697ba5409df770a21c2175 Mon Sep 17 00:00:00 2001 From: ParikKukreja Date: Tue, 15 Apr 2025 21:51:37 -0700 Subject: [PATCH 1/2] Add tutorial completion tracking to avoid showing tutorial multiple times --- .../daos/slick/DBTableDefinitions.scala | 12 ++++---- app/models/mission/MissionTable.scala | 29 ++++++++++++------- conf/evolutions/default/269.sql | 16 ++++++++++ 3 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 conf/evolutions/default/269.sql diff --git a/app/models/daos/slick/DBTableDefinitions.scala b/app/models/daos/slick/DBTableDefinitions.scala index f26ee62abe..84d1b7fbd0 100644 --- a/app/models/daos/slick/DBTableDefinitions.scala +++ b/app/models/daos/slick/DBTableDefinitions.scala @@ -6,13 +6,15 @@ import java.util.UUID object DBTableDefinitions { - case class DBUser (userId: String, username: String, email: String ) + case class DBUser(userId: String, username: String, email: String, tutorialCompleted: Boolean = false) + class UserTable(tag: Tag) extends Table[DBUser](tag, "sidewalk_user") { - def userId = column[String]("user_id", O.PrimaryKey) - def username = column[String]("username") - def email = column[String]("email") - def * = (userId, username, email) <> (DBUser.tupled, DBUser.unapply) + def userId = column[String]("user_id", O.PrimaryKey) + def username = column[String]("username") + def email = column[String]("email") + def tutorialCompleted = column[Boolean]("tutorial_completed") + def * = (userId, username, email, tutorialCompleted) <> (DBUser.tupled, DBUser.unapply) } case class DBLoginInfo (id: Option[Long], providerID: String, providerKey: String ) diff --git a/app/models/mission/MissionTable.scala b/app/models/mission/MissionTable.scala index 258f9f17a2..e733604e70 100644 --- a/app/models/mission/MissionTable.scala +++ b/app/models/mission/MissionTable.scala @@ -217,8 +217,11 @@ object MissionTable { * Check if the user has completed onboarding. */ def hasCompletedAuditOnboarding(userId: UUID): Boolean = db.withSession { implicit session => - selectCompletedMissions(userId, includeOnboarding = true, includeSkipped = true) - .exists(_.missionTypeId == MissionTypeTable.missionTypeToId("auditOnboarding")) + users.filter(_.userId === userId.toString).map(_.tutorialCompleted).first + } + + def markTutorialCompleted(userId: UUID): Int = db.withSession { implicit session => + users.filter(_.userId === userId.toString).map(_.tutorialCompleted).update(true) } /** @@ -611,19 +614,25 @@ object MissionTable { _missionType <- missionTypes if _mission.missionTypeId === _missionType.missionTypeId } yield _missionType.missionType).firstOption } - - /** - * Marks the specified mission as complete, filling in mission_end timestamp. - * - * NOTE only call from queryMissionTable or queryMissionTableValidationMissions funcs to prevent race conditions. - * - * @return Int number of rows updated (should always be 1). - */ + def updateComplete(missionId: Int): Int = db.withSession { implicit session => val now: Timestamp = new Timestamp(Instant.now.toEpochMilli) val missionToUpdate = for { m <- missions if m.missionId === missionId } yield (m.completed, m.missionEnd) val rowsUpdated: Int = missionToUpdate.update((true, now)) if (rowsUpdated == 0) Logger.error("Tried to mark a mission as complete, but no mission exists with that ID.") + + try { + // Check if this is an audit onboarding mission and mark tutorial as completed if it is + val missionType = getMissionType(missionId) + if (missionType.exists(_ == "auditOnboarding")) { + val userId = missions.filter(_.missionId === missionId).map(_.userId).first + markTutorialCompleted(UUID.fromString(userId)) + } + } catch { + case e: Exception => + Logger.error(s"Error updating tutorial completion status: ${e.getMessage}") + } + rowsUpdated } diff --git a/conf/evolutions/default/269.sql b/conf/evolutions/default/269.sql new file mode 100644 index 0000000000..5f89ac3d4b --- /dev/null +++ b/conf/evolutions/default/269.sql @@ -0,0 +1,16 @@ +# --- !Ups +ALTER TABLE sidewalk_user ADD COLUMN IF NOT EXISTS tutorial_completed BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE sidewalk_user AS su +SET tutorial_completed = TRUE +WHERE EXISTS ( + SELECT 1 + FROM mission AS m + JOIN mission_type AS mt ON m.mission_type_id = mt.mission_type_id + WHERE m.user_id = su.user_id + AND mt.mission_type = 'auditOnboarding' + AND m.completed = TRUE +); + +# --- !Downs +ALTER TABLE sidewalk_user DROP COLUMN IF EXISTS tutorial_completed; \ No newline at end of file From 51886677b95e48e20b01ec9c069bf3fa4e38db4b Mon Sep 17 00:00:00 2001 From: ParikKukreja Date: Tue, 15 Apr 2025 21:56:44 -0700 Subject: [PATCH 2/2] comments, indents, newlines, etc --- app/models/daos/slick/DBTableDefinitions.scala | 10 +++++----- app/models/mission/MissionTable.scala | 14 +++++++++++++- conf/evolutions/default/269.sql | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/models/daos/slick/DBTableDefinitions.scala b/app/models/daos/slick/DBTableDefinitions.scala index 84d1b7fbd0..4149ae95c9 100644 --- a/app/models/daos/slick/DBTableDefinitions.scala +++ b/app/models/daos/slick/DBTableDefinitions.scala @@ -10,11 +10,11 @@ object DBTableDefinitions { class UserTable(tag: Tag) extends Table[DBUser](tag, "sidewalk_user") { - def userId = column[String]("user_id", O.PrimaryKey) - def username = column[String]("username") - def email = column[String]("email") - def tutorialCompleted = column[Boolean]("tutorial_completed") - def * = (userId, username, email, tutorialCompleted) <> (DBUser.tupled, DBUser.unapply) + def userId = column[String]("user_id", O.PrimaryKey) + def username = column[String]("username") + def email = column[String]("email") + def tutorialCompleted = column[Boolean]("tutorial_completed") + def * = (userId, username, email, tutorialCompleted) <> (DBUser.tupled, DBUser.unapply) } case class DBLoginInfo (id: Option[Long], providerID: String, providerKey: String ) diff --git a/app/models/mission/MissionTable.scala b/app/models/mission/MissionTable.scala index e733604e70..c20784a420 100644 --- a/app/models/mission/MissionTable.scala +++ b/app/models/mission/MissionTable.scala @@ -220,6 +220,9 @@ object MissionTable { users.filter(_.userId === userId.toString).map(_.tutorialCompleted).first } + /** + * Updates the user's profile to mark the tutorial as completed. + */ def markTutorialCompleted(userId: UUID): Int = db.withSession { implicit session => users.filter(_.userId === userId.toString).map(_.tutorialCompleted).update(true) } @@ -614,7 +617,16 @@ object MissionTable { _missionType <- missionTypes if _mission.missionTypeId === _missionType.missionTypeId } yield _missionType.missionType).firstOption } - + + /** + * Marks the specified mission as complete, filling in mission_end timestamp. + * If the mission is an audit onboarding mission, also marks the tutorial as completed + * in the user's profile to prevent it from appearing again in any city. + * + * NOTE only call from queryMissionTable or queryMissionTableValidationMissions funcs to prevent race conditions. + * + * @return Int number of rows updated (should always be 1). + */ def updateComplete(missionId: Int): Int = db.withSession { implicit session => val now: Timestamp = new Timestamp(Instant.now.toEpochMilli) val missionToUpdate = for { m <- missions if m.missionId === missionId } yield (m.completed, m.missionEnd) diff --git a/conf/evolutions/default/269.sql b/conf/evolutions/default/269.sql index 5f89ac3d4b..991a1ce9fd 100644 --- a/conf/evolutions/default/269.sql +++ b/conf/evolutions/default/269.sql @@ -13,4 +13,4 @@ WHERE EXISTS ( ); # --- !Downs -ALTER TABLE sidewalk_user DROP COLUMN IF EXISTS tutorial_completed; \ No newline at end of file +ALTER TABLE sidewalk_user DROP COLUMN IF EXISTS tutorial_completed;