feat(grouping): Enable secondary grouping with upgrade
diff --git a/src/sentry/api/endpoints/project_details.py b/src/sentry/api/endpoints/project_details.py
index fb681a400801d..7326c93106699 100644
--- a/src/sentry/api/endpoints/project_details.py
+++ b/src/sentry/api/endpoints/project_details.py
@@ -260,11 +260,16 @@ def validate_secondaryGroupingExpiry(self, value):
             raise serializers.ValidationError(
                 f"Grouping expiry must be a numerical value, a UNIX timestamp with second resolution, found {type(value)}"
             )
-        if not (0 < value - time.time() < (91 * 24 * 3600)):
+        now = time.time()
+        if value < now:
             raise serializers.ValidationError(
                 "Grouping expiry must be sometime within the next 90 days and not in the past. Perhaps you specified the timestamp not in seconds?"
             )
 
+        max_expiry_date = now + (91 * 24 * 3600)
+        if value > max_expiry_date:
+            value = max_expiry_date
+
         return value
 
     def validate_fingerprintingRules(self, value):
diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py
index b19baadd56a83..4f1910fd5e5cc 100644
--- a/src/sentry/api/serializers/models/project.py
+++ b/src/sentry/api/serializers/models/project.py
@@ -648,6 +648,8 @@ class DetailedProjectSerializer(ProjectWithTeamSerializer):
             "sentry:grouping_config",
             "sentry:grouping_enhancements",
             "sentry:grouping_enhancements_base",
+            "sentry:secondary_grouping_config",
+            "sentry:secondary_grouping_expiry",
             "sentry:fingerprinting_rules",
             "sentry:relay_pii_config",
             "sentry:dynamic_sampling",
@@ -793,6 +795,7 @@ def get_value_with_default(key):
                 "dynamicSampling": get_value_with_default("sentry:dynamic_sampling"),
             }
         )
+
         return data
 
 
diff --git a/src/sentry/event_manager.py b/src/sentry/event_manager.py
index be739ec81a8a8..235fe7e38d1db 100644
--- a/src/sentry/event_manager.py
+++ b/src/sentry/event_manager.py
@@ -356,7 +356,9 @@ def save(self, project_id, raw=False, assume_normalized=False, start_time=None,
         secondary_hashes = None
 
         try:
-            if (project.get_option("sentry:secondary_grouping_expiry") or 0) >= time.time():
+            secondary_grouping_config = project.get_option("sentry:secondary_grouping_config")
+            secondary_grouping_expiry = project.get_option("sentry:secondary_grouping_expiry")
+            if secondary_grouping_config and (secondary_grouping_expiry or 0) >= time.time():
                 with metrics.timer("event_manager.secondary_grouping"):
                     secondary_event = copy.deepcopy(job["event"])
                     loader = SecondaryGroupingConfigLoader()
diff --git a/static/app/views/settings/projectIssueGrouping/upgradeGrouping.tsx b/static/app/views/settings/projectIssueGrouping/upgradeGrouping.tsx
index d8243651cb279..3fc1b9634f4b0 100644
--- a/static/app/views/settings/projectIssueGrouping/upgradeGrouping.tsx
+++ b/static/app/views/settings/projectIssueGrouping/upgradeGrouping.tsx
@@ -41,9 +41,14 @@ function UpgradeGrouping({
   const {riskNote, alertType} = getGroupingRisk(riskLevel);
   const noUpdates = !latestGroupingConfig;
 
-  const newData: Record<string, string> = {};
+  const newData: Record<string, string | number> = {};
   if (latestGroupingConfig) {
+    const now = Math.floor(new Date().getTime() / 1000);
+    const ninety_days = 3600 * 24 * 90;
+
     newData.groupingConfig = latestGroupingConfig.id;
+    newData.secondaryGroupingConfig = project.groupingConfig;
+    newData.secondaryGroupingExpiry = now + ninety_days;
   }
 
   const handleUpgrade = async () => {
diff --git a/tests/sentry/api/endpoints/test_project_details.py b/tests/sentry/api/endpoints/test_project_details.py
index 3990211101c6d..82ff88263b1b7 100644
--- a/tests/sentry/api/endpoints/test_project_details.py
+++ b/tests/sentry/api/endpoints/test_project_details.py
@@ -1,3 +1,5 @@
+from time import time
+
 import pytest
 
 from sentry.api.endpoints.project_details import (
@@ -722,6 +724,32 @@ def test_dynamic_sampling_rule_id_handling(self):
         new_next_id = saved_config["next_id"]
         assert new_next_id == 7
 
+    def test_cap_secondary_grouping_expiry(self):
+        now = time()
+
+        response = self.get_response(self.org_slug, self.proj_slug, secondaryGroupingExpiry=0)
+        assert response.status_code == 400
+
+        expiry = int(now + 3600 * 24 * 1)
+        response = self.get_valid_response(
+            self.org_slug, self.proj_slug, secondaryGroupingExpiry=expiry
+        )
+        assert response.data["secondaryGroupingExpiry"] == expiry
+
+        expiry = int(now + 3600 * 24 * 89)
+        response = self.get_valid_response(
+            self.org_slug, self.proj_slug, secondaryGroupingExpiry=expiry
+        )
+        assert response.data["secondaryGroupingExpiry"] == expiry
+
+        # Larger timestamps are capped to 91 days:
+        expiry = int(now + 3600 * 24 * 365)
+        response = self.get_valid_response(
+            self.org_slug, self.proj_slug, secondaryGroupingExpiry=expiry
+        )
+        expiry = response.data["secondaryGroupingExpiry"]
+        assert (now + 3600 * 24 * 90) < expiry < (now + 3600 * 24 * 92)
+
 
 class CopyProjectSettingsTest(APITestCase):
     endpoint = "sentry-api-0-project-details"

When upgrading to a new grouping strategy, keep the old strategy alive
as secondary grouping  for 90 days in order to reduce the number of newly created groups.

Fix https://getsentry.atlassian.net/browse/INGEST-216