|
14 | 14 | from sentry.models.organization import Organization |
15 | 15 | from sentry.models.project import Project |
16 | 16 | from sentry.search.events.types import SnubaParams |
| 17 | +from sentry.seer.autofix.utils import ( |
| 18 | + bulk_get_project_preferences, |
| 19 | + get_autofix_repos_from_project_code_mappings, |
| 20 | +) |
17 | 21 | from sentry.seer.explorer.context_engine_utils import ( |
18 | 22 | EVENT_COUNT_LOOKBACK_DAYS, |
19 | 23 | ProjectEventCounts, |
|
30 | 34 | ) |
31 | 35 | from sentry.seer.models import SeerApiError |
32 | 36 | from sentry.seer.signed_seer_api import ( |
| 37 | + ExplorerIndexOrgRepoRequest, |
33 | 38 | ExplorerIndexSentryKnowledgeRequest, |
34 | 39 | OrgProjectKnowledgeIndexRequest, |
35 | 40 | OrgProjectKnowledgeProjectData, |
| 41 | + RepoDetails, |
36 | 42 | SeerViewerContext, |
37 | 43 | make_index_sentry_knowledge_request, |
38 | 44 | make_org_project_knowledge_index_request, |
| 45 | + make_org_repo_knowledge_index_request, |
39 | 46 | ) |
40 | 47 | from sentry.tasks.base import instrumented_task |
41 | 48 | from sentry.taskworker.namespaces import seer_tasks |
@@ -213,6 +220,90 @@ def build_service_map(organization_id: int, *args, **kwargs) -> None: |
213 | 220 | raise |
214 | 221 |
|
215 | 222 |
|
| 223 | +@instrumented_task( |
| 224 | + name="sentry.tasks.seer.context_engine_index.index_repos", |
| 225 | + namespace=seer_tasks, |
| 226 | + processing_deadline_duration=10 * 60, # 10 minutes |
| 227 | + retry=Retry(times=3, on=(SeerApiError,), delay=60), |
| 228 | +) |
| 229 | +def index_repos(organization_id: int, *args, **kwargs) -> None: |
| 230 | + if not options.get("explorer.context_engine_indexing.enable"): |
| 231 | + logger.info("explorer.context_engine_indexing.enable flag is disabled") |
| 232 | + return |
| 233 | + |
| 234 | + try: |
| 235 | + organization = Organization.objects.get(id=organization_id) |
| 236 | + except Organization.DoesNotExist: |
| 237 | + logger.error("Organization not found", extra={"org_id": organization_id}) |
| 238 | + return |
| 239 | + |
| 240 | + if not features.has("organizations:context-engine-experiments", organization): |
| 241 | + logger.info("organizations:context-engine-experiments flag is disabled") |
| 242 | + return |
| 243 | + |
| 244 | + logger.info( |
| 245 | + "Starting repo index task", |
| 246 | + extra={"org_id": organization_id}, |
| 247 | + ) |
| 248 | + |
| 249 | + projects = list( |
| 250 | + Project.objects.filter(organization_id=organization_id, status=ObjectStatus.ACTIVE) |
| 251 | + ) |
| 252 | + project_map = {p.id: p for p in projects} |
| 253 | + |
| 254 | + if not project_map: |
| 255 | + logger.info("No projects found for organization", extra={"org_id": organization_id}) |
| 256 | + return |
| 257 | + |
| 258 | + org_repo_definitions: dict[tuple[str, str, str], RepoDetails] = {} |
| 259 | + |
| 260 | + preferences_by_id = bulk_get_project_preferences(organization_id, list(project_map.keys())) |
| 261 | + |
| 262 | + for project_id, project in project_map.items(): |
| 263 | + existing_pref = preferences_by_id.get(str(project_id)) |
| 264 | + if not existing_pref: |
| 265 | + continue |
| 266 | + |
| 267 | + project_pref_repos = existing_pref.get("repositories") or [] |
| 268 | + |
| 269 | + autofix_repos = get_autofix_repos_from_project_code_mappings(project) |
| 270 | + # Use autofix repos to get repo languages |
| 271 | + language_map: dict[tuple[str, str, str], list[str]] = {} |
| 272 | + for autofix_repo in autofix_repos: |
| 273 | + key = (autofix_repo["provider"], autofix_repo["owner"], autofix_repo["name"]) |
| 274 | + language_map[key] = autofix_repo["languages"] |
| 275 | + |
| 276 | + for repo in project_pref_repos: |
| 277 | + key = (repo["provider"], repo["owner"], repo["name"]) |
| 278 | + if key in org_repo_definitions: |
| 279 | + repo_definition = org_repo_definitions[key] |
| 280 | + repo_definition["project_ids"].append(project_id) |
| 281 | + else: |
| 282 | + org_repo_definitions[key] = { |
| 283 | + "project_ids": [project_id], |
| 284 | + "provider": repo["provider"], |
| 285 | + "owner": repo["owner"], |
| 286 | + "name": repo["name"], |
| 287 | + "external_id": repo["external_id"], |
| 288 | + "languages": language_map.get(key, []), |
| 289 | + "integration_id": repo.get("integration_id"), |
| 290 | + } |
| 291 | + |
| 292 | + viewer_context = SeerViewerContext(organization_id=organization_id) |
| 293 | + response = make_org_repo_knowledge_index_request( |
| 294 | + ExplorerIndexOrgRepoRequest( |
| 295 | + org_id=organization.id, repos=list(org_repo_definitions.values()) |
| 296 | + ), |
| 297 | + timeout=30, |
| 298 | + viewer_context=viewer_context, |
| 299 | + ) |
| 300 | + |
| 301 | + if response.status >= 400: |
| 302 | + raise SeerApiError("Seer request failed", response.status) |
| 303 | + |
| 304 | + logger.info("Successfully indexed repos for org", extra={"org_id": organization_id}) |
| 305 | + |
| 306 | + |
216 | 307 | def get_allowed_org_ids_context_engine_indexing() -> list[int]: |
217 | 308 | """ |
218 | 309 | Get the list of allowed organizations for context engine indexing. |
@@ -283,12 +374,17 @@ def schedule_context_engine_indexing_tasks() -> None: |
283 | 374 | return |
284 | 375 |
|
285 | 376 | allowed_org_ids = get_allowed_org_ids_context_engine_indexing() |
| 377 | + now = datetime.now(UTC) |
286 | 378 |
|
287 | 379 | dispatched = 0 |
288 | 380 | for org_id in allowed_org_ids: |
289 | 381 | try: |
290 | 382 | index_org_project_knowledge.apply_async(args=[org_id]) |
291 | 383 | build_service_map.apply_async(args=[org_id]) |
| 384 | + |
| 385 | + if now.weekday() == 6: # Sunday |
| 386 | + index_repos.apply_async(args=[org_id]) |
| 387 | + |
292 | 388 | dispatched += 1 |
293 | 389 | except Exception: |
294 | 390 | logger.exception( |
|
0 commit comments