Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/services/star_svc.py b/services/star_svc.py
index bb92e73..4ef045e 100644
--- a/services/star_svc.py
+++ b/services/star_svc.py
@@ -1,7 +1,6 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file or at
-# https://developers.google.com/open-source/licenses/bsd
+# Copyright 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """A set of functions that provide persistence for stars.
 
@@ -13,7 +12,7 @@
 
 import logging
 
-import settings
+import time
 from features import filterrules_helpers
 from framework import sql
 from services import caches
@@ -55,7 +54,14 @@
     self.star_count_cache = caches.RamCache(cache_manager, cache_kind)
 
   def ExpungeStars(self, cnxn, item_id, commit=True, limit=None):
-    """Wipes an item's stars from the system."""
+    """Wipes an item's stars from the system.
+
+    Args:
+      cnxn: connection to SQL database.
+      item_id: ID of the item that's starred. ie: an issue, project, etc
+      commit: whether to commit the change.
+      limit: max stars to delete for performance reasons.
+    """
     self.tbl.Delete(
         cnxn, commit=commit, limit=limit, **{self.item_col: item_id})
 
@@ -159,7 +165,6 @@
     self._SetStarsBatch(cnxn, item_id, [starrer_user_id], starred)
 
 
-
 class UserStarService(AbstractStarService):
   """Star service for stars on users."""
 
@@ -195,6 +200,46 @@
     super(IssueStarService, self).__init__(
         cache_manager, tbl, 'issue_id', 'user_id', 'issue')
 
+    # HACK. Usually Monorail SQL table references should stay in their
+    # respective service layer class. But for performance reasons, it's better
+    # for us to directly query the Issue table here.
+    self.issue_tbl = sql.SQLTableManager('Issue')
+
+  def ExpungeStarsByUsers(self, cnxn, user_ids, limit=None):
+    """Wipes a user's stars from the system.
+
+    Ensure that issue metadata is updated on expunging.
+
+    Args:
+      cnxn: connection to SQL database.
+      services:  connections to persistence layer.
+      user_ids: users to delete stars for.
+      limit: max stars to delete for performance reasons.
+    """
+    # TODO(zhangtiff): update star_count for updated issues. This is tricky
+    # because star_count needs to be recomputd for each issue, so this likely
+    # requires a task queue.
+
+    timestamp = int(time.time())
+
+    shard_id = sql.RandomShardID()
+    issue_id_rows = self.tbl.Select(
+        cnxn,
+        cols=['IssueStar.issue_id'],
+        user_id=user_ids,
+        shard_id=shard_id,
+        limit=limit)
+
+    super(IssueStarService, self).ExpungeStarsByUsers(
+        cnxn, user_ids, limit=limit)
+    issue_ids = [row[0] for row in issue_id_rows]
+    if issue_ids:
+      self.issue_tbl.Update(
+          cnxn, {'migration_modified': timestamp},
+          id=issue_ids,
+          commit=False,
+          limit=limit)
+
   # pylint: disable=arguments-differ
   def SetStar(
       self, cnxn, services, config, issue_id, starrer_user_id, starred):
@@ -232,6 +277,7 @@
     # Because we will modify issues, load from DB rather than cache.
     issue = services.issue.GetIssue(cnxn, issue_id, use_cache=False)
     issue.star_count = self.CountItemStars(cnxn, issue_id)
+    issue.migration_modified_timestamp = int(time.time())
     filterrules_helpers.ApplyFilterRules(cnxn, services, issue, config)
     # Note: only star_count could change due to the starring, but any
     # field could have changed as a result of filter rules.