Skip to content

Commit 551660b

Browse files
tw4likreymer
andauthored
Add webhooks for qaAnalysisStarted, qaAnalysisFinished, and crawlReviewed (#1974)
Fixes #1957 Adds three new webhook events related to QA: analysis started, analysis ended, and crawl reviewed. Tests have been updated accordingly. --------- Co-authored-by: Ilya Kreymer <[email protected]>
1 parent daeb744 commit 551660b

File tree

6 files changed

+266
-13
lines changed

6 files changed

+266
-13
lines changed

backend/btrixcloud/basecrawls.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import asyncio
1010
from fastapi import HTTPException, Depends
1111
from fastapi.responses import StreamingResponse
12+
import pymongo
1213

1314
from .models import (
1415
CrawlFile,
@@ -244,13 +245,19 @@ async def update_crawl(
244245

245246
# update in db
246247
result = await self.crawls.find_one_and_update(
247-
query,
248-
{"$set": update_values},
248+
query, {"$set": update_values}, return_document=pymongo.ReturnDocument.AFTER
249249
)
250250

251251
if not result:
252252
raise HTTPException(status_code=404, detail="crawl_not_found")
253253

254+
if update_values.get("reviewStatus"):
255+
crawl = BaseCrawl.from_dict(result)
256+
257+
await self.event_webhook_ops.create_crawl_reviewed_notification(
258+
crawl.id, crawl.oid, crawl.reviewStatus, crawl.description
259+
)
260+
254261
return {"updated": True}
255262

256263
async def update_crawl_state(self, crawl_id: str, state: str):

backend/btrixcloud/crawls.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -929,12 +929,15 @@ async def qa_run_finished(self, crawl_id: str) -> bool:
929929
if crawl.qa.finished and crawl.qa.state in NON_RUNNING_STATES:
930930
query[f"qaFinished.{crawl.qa.id}"] = crawl.qa.dict()
931931

932-
if await self.crawls.find_one_and_update(
932+
res = await self.crawls.find_one_and_update(
933933
{"_id": crawl_id, "type": "crawl"}, {"$set": query}
934-
):
935-
return True
934+
)
935+
936+
await self.event_webhook_ops.create_qa_analysis_finished_notification(
937+
crawl.qa, crawl.oid, crawl.id
938+
)
936939

937-
return False
940+
return res
938941

939942
async def get_qa_runs(
940943
self,

backend/btrixcloud/models.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,9 @@ class OrgWebhookUrls(BaseModel):
12771277
crawlStarted: Optional[AnyHttpUrl] = None
12781278
crawlFinished: Optional[AnyHttpUrl] = None
12791279
crawlDeleted: Optional[AnyHttpUrl] = None
1280+
qaAnalysisStarted: Optional[AnyHttpUrl] = None
1281+
qaAnalysisFinished: Optional[AnyHttpUrl] = None
1282+
crawlReviewed: Optional[AnyHttpUrl] = None
12801283
uploadFinished: Optional[AnyHttpUrl] = None
12811284
uploadDeleted: Optional[AnyHttpUrl] = None
12821285
addedToCollection: Optional[AnyHttpUrl] = None
@@ -1735,6 +1738,11 @@ class WebhookEventType(str, Enum):
17351738
CRAWL_FINISHED = "crawlFinished"
17361739
CRAWL_DELETED = "crawlDeleted"
17371740

1741+
QA_ANALYSIS_STARTED = "qaAnalysisStarted"
1742+
QA_ANALYSIS_FINISHED = "qaAnalysisFinished"
1743+
1744+
CRAWL_REVIEWED = "crawlReviewed"
1745+
17381746
UPLOAD_FINISHED = "uploadFinished"
17391747
UPLOAD_DELETED = "uploadDeleted"
17401748

@@ -1831,6 +1839,39 @@ class UploadDeletedBody(BaseArchivedItemBody):
18311839
event: Literal[WebhookEventType.UPLOAD_DELETED] = WebhookEventType.UPLOAD_DELETED
18321840

18331841

1842+
# ============================================================================
1843+
class QaAnalysisStartedBody(BaseArchivedItemBody):
1844+
"""Webhook notification POST body for when qa analysis run starts"""
1845+
1846+
event: Literal[WebhookEventType.QA_ANALYSIS_STARTED] = (
1847+
WebhookEventType.QA_ANALYSIS_STARTED
1848+
)
1849+
1850+
qaRunId: str
1851+
1852+
1853+
# ============================================================================
1854+
class QaAnalysisFinishedBody(BaseArchivedItemFinishedBody):
1855+
"""Webhook notification POST body for when qa analysis run finishes"""
1856+
1857+
event: Literal[WebhookEventType.QA_ANALYSIS_FINISHED] = (
1858+
WebhookEventType.QA_ANALYSIS_FINISHED
1859+
)
1860+
1861+
qaRunId: str
1862+
1863+
1864+
# ============================================================================
1865+
class CrawlReviewedBody(BaseArchivedItemBody):
1866+
"""Webhook notification POST body for when crawl is reviewed in qa"""
1867+
1868+
event: Literal[WebhookEventType.CRAWL_REVIEWED] = WebhookEventType.CRAWL_REVIEWED
1869+
1870+
reviewStatus: ReviewStatus
1871+
reviewStatusLabel: str
1872+
description: Optional[str] = None
1873+
1874+
18341875
# ============================================================================
18351876
class WebhookNotification(BaseMongoModel):
18361877
"""Base POST body model for webhook notifications"""
@@ -1841,6 +1882,9 @@ class WebhookNotification(BaseMongoModel):
18411882
CrawlStartedBody,
18421883
CrawlFinishedBody,
18431884
CrawlDeletedBody,
1885+
QaAnalysisStartedBody,
1886+
QaAnalysisFinishedBody,
1887+
CrawlReviewedBody,
18441888
UploadFinishedBody,
18451889
UploadDeletedBody,
18461890
CollectionItemAddedBody,

backend/btrixcloud/operator/crawls.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ async def finalize_response(
720720
finalized = True
721721

722722
if finalized and crawl.is_qa:
723-
await self.crawl_ops.qa_run_finished(crawl.db_crawl_id)
723+
self.run_task(self.crawl_ops.qa_run_finished(crawl.db_crawl_id))
724724

725725
return {
726726
"status": status.dict(exclude_none=True),
@@ -816,11 +816,18 @@ async def sync_crawl_state(
816816
crawl,
817817
allowed_from=["starting", "waiting_capacity"],
818818
):
819-
self.run_task(
820-
self.event_webhook_ops.create_crawl_started_notification(
821-
crawl.id, crawl.oid, scheduled=crawl.scheduled
819+
if not crawl.qa_source_crawl_id:
820+
self.run_task(
821+
self.event_webhook_ops.create_crawl_started_notification(
822+
crawl.id, crawl.oid, scheduled=crawl.scheduled
823+
)
824+
)
825+
else:
826+
self.run_task(
827+
self.event_webhook_ops.create_qa_analysis_started_notification(
828+
crawl.id, crawl.oid, crawl.qa_source_crawl_id
829+
)
822830
)
823-
)
824831

825832
# update lastActiveTime if crawler is running
826833
if crawler_running:

backend/btrixcloud/webhooks.py

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
CrawlStartedBody,
1616
CrawlFinishedBody,
1717
CrawlDeletedBody,
18+
QaAnalysisStartedBody,
19+
QaAnalysisFinishedBody,
20+
CrawlReviewedBody,
1821
UploadFinishedBody,
1922
UploadDeletedBody,
2023
CollectionItemAddedBody,
2124
CollectionItemRemovedBody,
2225
CollectionDeletedBody,
2326
PaginatedWebhookNotificationResponse,
2427
Organization,
28+
QARun,
2529
)
2630
from .utils import dt_now
2731

@@ -195,7 +199,7 @@ async def _create_item_finished_notification(
195199
crawl_id: str,
196200
org: Organization,
197201
event: str,
198-
body: Union[CrawlFinishedBody, UploadFinishedBody],
202+
body: Union[CrawlFinishedBody, QaAnalysisFinishedBody, UploadFinishedBody],
199203
):
200204
"""Create webhook notification for finished crawl/upload."""
201205
crawl = await self.crawl_ops.get_crawl_out(crawl_id, org)
@@ -263,6 +267,46 @@ async def create_crawl_finished_notification(
263267
),
264268
)
265269

270+
async def create_qa_analysis_finished_notification(
271+
self, qa_run: QARun, oid: UUID, crawl_id: str
272+
) -> None:
273+
"""Create webhook notification for finished qa analysis run."""
274+
org = await self.org_ops.get_org_by_id(oid)
275+
276+
if not org.webhookUrls or not org.webhookUrls.qaAnalysisFinished:
277+
return
278+
279+
qa_resources = []
280+
281+
# Check both crawl.qa and crawl.qaFinished for files because we don't
282+
# know for certain what state the crawl will be in at this point
283+
try:
284+
qa_resources = await self.crawl_ops.resolve_signed_urls(
285+
qa_run.files, org, crawl_id, qa_run.id
286+
)
287+
288+
# pylint: disable=broad-exception-caught
289+
except Exception as err:
290+
print(f"Error trying to get QA run resources: {err}", flush=True)
291+
292+
notification = WebhookNotification(
293+
id=uuid4(),
294+
event=WebhookEventType.QA_ANALYSIS_FINISHED,
295+
oid=oid,
296+
body=QaAnalysisFinishedBody(
297+
itemId=crawl_id,
298+
qaRunId=qa_run.id,
299+
orgId=str(org.id),
300+
state=qa_run.state,
301+
resources=qa_resources,
302+
),
303+
created=dt_now(),
304+
)
305+
306+
await self.webhooks.insert_one(notification.to_dict())
307+
308+
await self.send_notification(org, notification)
309+
266310
async def create_crawl_deleted_notification(
267311
self, crawl_id: str, org: Organization
268312
) -> None:
@@ -345,6 +389,82 @@ async def create_crawl_started_notification(
345389

346390
await self.send_notification(org, notification)
347391

392+
async def create_qa_analysis_started_notification(
393+
self, qa_run_id: str, oid: UUID, crawl_id: str
394+
) -> None:
395+
"""Create webhook notification for started qa analysis run."""
396+
org = await self.org_ops.get_org_by_id(oid)
397+
398+
if not org.webhookUrls or not org.webhookUrls.qaAnalysisStarted:
399+
return
400+
401+
# Check if already created this event
402+
existing_notification = await self.webhooks.find_one(
403+
{
404+
"event": WebhookEventType.QA_ANALYSIS_STARTED,
405+
"body.qaRunId": qa_run_id,
406+
}
407+
)
408+
if existing_notification:
409+
return
410+
411+
notification = WebhookNotification(
412+
id=uuid4(),
413+
event=WebhookEventType.QA_ANALYSIS_STARTED,
414+
oid=oid,
415+
body=QaAnalysisStartedBody(
416+
itemId=crawl_id,
417+
qaRunId=qa_run_id,
418+
orgId=str(oid),
419+
),
420+
created=dt_now(),
421+
)
422+
423+
await self.webhooks.insert_one(notification.to_dict())
424+
425+
await self.send_notification(org, notification)
426+
427+
async def create_crawl_reviewed_notification(
428+
self,
429+
crawl_id: str,
430+
oid: UUID,
431+
review_status: Optional[int],
432+
description: Optional[str],
433+
) -> None:
434+
"""Create webhook notification for crawl being reviewed in qa"""
435+
org = await self.org_ops.get_org_by_id(oid)
436+
437+
if not org.webhookUrls or not org.webhookUrls.crawlReviewed:
438+
return
439+
440+
review_status_labels = {
441+
1: "Bad",
442+
2: "Poor",
443+
3: "Fair",
444+
4: "Good",
445+
5: "Excellent",
446+
}
447+
448+
notification = WebhookNotification(
449+
id=uuid4(),
450+
event=WebhookEventType.CRAWL_REVIEWED,
451+
oid=oid,
452+
body=CrawlReviewedBody(
453+
itemId=crawl_id,
454+
orgId=str(oid),
455+
reviewStatus=review_status,
456+
reviewStatusLabel=(
457+
review_status_labels.get(review_status, "") if review_status else ""
458+
),
459+
description=description,
460+
),
461+
created=dt_now(),
462+
)
463+
464+
await self.webhooks.insert_one(notification.to_dict())
465+
466+
await self.send_notification(org, notification)
467+
348468
async def _create_collection_items_modified_notification(
349469
self,
350470
coll_id: UUID,
@@ -507,6 +627,18 @@ def crawl_finished(body: CrawlFinishedBody):
507627
def crawl_deleted(body: CrawlDeletedBody):
508628
"""Sent when a crawl is deleted"""
509629

630+
@app.webhooks.post(WebhookEventType.QA_ANALYSIS_STARTED)
631+
def qa_analysis_started(body: QaAnalysisStartedBody):
632+
"""Sent when a qa analysis run is started"""
633+
634+
@app.webhooks.post(WebhookEventType.QA_ANALYSIS_FINISHED)
635+
def qa_analysis_finished(body: QaAnalysisFinishedBody):
636+
"""Sent when a qa analysis run has finished"""
637+
638+
@app.webhooks.post(WebhookEventType.CRAWL_REVIEWED)
639+
def crawl_reviewed(body: CrawlReviewedBody):
640+
"""Sent when a crawl has been reviewed in qa"""
641+
510642
@app.webhooks.post(WebhookEventType.UPLOAD_FINISHED)
511643
def upload_finished(body: UploadFinishedBody):
512644
"""Sent when an upload has finished"""

0 commit comments

Comments
 (0)