Skip to content

Commit c46c2b1

Browse files
committed
Delete previous Q&A pair if no feedback received using atomic operation
1 parent 8018e71 commit c46c2b1

File tree

2 files changed

+52
-56
lines changed

2 files changed

+52
-56
lines changed

packages/slackBotFunction/app/slack/slack_events.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,39 +34,29 @@
3434

3535

3636
def cleanup_previous_unfeedback_qa(conversation_key, current_message_ts, session_data):
37-
"""Delete previous Q&A pair if no feedback received"""
37+
"""Delete previous Q&A pair if no feedback received using atomic operation"""
3838
try:
3939
previous_message_ts = session_data.get("latest_message_ts")
4040
# Skip if no previous message or it's the same as current
4141
if not previous_message_ts or previous_message_ts == current_message_ts:
4242
return
4343

44-
# Check if previous Q&A has any feedback
45-
feedback_exists = check_feedback_exists(conversation_key, previous_message_ts)
46-
47-
if not feedback_exists:
48-
# Delete unfeedback Q&A pair for privacy compliance
49-
previous_qa_key = f"qa#{conversation_key}#{previous_message_ts}"
50-
table.delete_item(Key={"pk": previous_qa_key, "sk": "turn"})
51-
logger.info("Deleted unfeedback Q&A for privacy", extra={"message_ts": previous_message_ts})
44+
# Atomically delete Q&A only if no feedback received
45+
table.delete_item(
46+
Key={"pk": f"qa#{conversation_key}#{previous_message_ts}", "sk": "turn"},
47+
ConditionExpression="attribute_not_exists(feedback_received)",
48+
)
49+
logger.info("Deleted unfeedback Q&A for privacy", extra={"message_ts": previous_message_ts})
5250

51+
except ClientError as e:
52+
if e.response.get("Error", {}).get("Code") == "ConditionalCheckFailedException":
53+
logger.info("Q&A has feedback - keeping for user", extra={"message_ts": previous_message_ts})
54+
else:
55+
logger.error("Error cleaning up Q&A", extra={"error": str(e)})
5356
except Exception as e:
5457
logger.error("Error cleaning up unfeedback Q&A", extra={"error": str(e)})
5558

5659

57-
def check_feedback_exists(conversation_key, message_ts):
58-
"""Check if any feedback exists for this Q&A pair"""
59-
try:
60-
response = table.query(
61-
KeyConditionExpression="pk = :pk",
62-
ExpressionAttributeValues={":pk": f"feedback#{conversation_key}#{message_ts}"},
63-
Limit=1,
64-
)
65-
return len(response.get("Items", [])) > 0
66-
except Exception:
67-
return False
68-
69-
7060
def store_qa_pair(conversation_key, user_query, bot_response, message_ts, session_id, user_id):
7161
"""
7262
Store Q&A pair for feedback correlation
@@ -308,6 +298,11 @@ def store_feedback_with_qa(
308298
feedback_item["additional_feedback"] = additional_feedback[:4000]
309299

310300
table.put_item(Item=feedback_item)
301+
302+
# Mark Q&A as having received feedback to prevent deletion
303+
if message_ts:
304+
_mark_qa_feedback_received(conversation_key, message_ts)
305+
311306
logger.info("Stored feedback with Q&A context", extra={"pk": pk, "sk": sk})
312307

313308
except Exception as e:
@@ -357,7 +352,7 @@ def store_feedback(
357352
"pk": pk,
358353
"sk": sk,
359354
"conversation_key": conversation_key,
360-
"feedback_type": feedback_type, # 'positive' | 'negative' | 'additional'
355+
"feedback_type": feedback_type,
361356
"user_id": user_id,
362357
"channel_id": channel_id,
363358
"created_at": now,
@@ -370,15 +365,19 @@ def store_feedback(
370365
if message_ts:
371366
feedback_item["message_ts"] = message_ts
372367
if user_query:
373-
feedback_item["user_query"] = user_query[:1000] # small excerpt to keep items compact
368+
feedback_item["user_query"] = user_query[:1000] # Truncate to keep items compact
374369
if additional_feedback:
375-
feedback_item["additional_feedback"] = additional_feedback[:4000]
370+
feedback_item["additional_feedback"] = additional_feedback[:4000] # Truncate to keep items compact
376371

377372
if condition:
378373
table.put_item(Item=feedback_item, ConditionExpression=condition)
379374
else:
380375
table.put_item(Item=feedback_item)
381376

377+
# Mark Q&A as having received feedback to prevent deletion
378+
if message_ts:
379+
_mark_qa_feedback_received(conversation_key, message_ts)
380+
382381
logger.info(
383382
"Stored feedback",
384383
extra={
@@ -488,6 +487,18 @@ def update_session_latest_message(conversation_key, message_ts):
488487
logger.error("Error updating session latest message", extra={"error": str(e)})
489488

490489

490+
def _mark_qa_feedback_received(conversation_key, message_ts):
491+
"""Mark Q&A record as having received feedback to prevent deletion"""
492+
try:
493+
table.update_item(
494+
Key={"pk": f"qa#{conversation_key}#{message_ts}", "sk": "turn"},
495+
UpdateExpression="SET feedback_received = :val",
496+
ExpressionAttributeValues={":val": True},
497+
)
498+
except Exception as e:
499+
logger.error("Error marking Q&A feedback received", extra={"error": str(e)})
500+
501+
491502
def query_bedrock(user_query, session_id=None):
492503
"""
493504
Query Amazon Bedrock Knowledge Base using RAG (Retrieval-Augmented Generation)

packages/slackBotFunction/tests/test_feedback.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -136,39 +136,24 @@ def test_cleanup_previous_unfeedback_qa(mock_table, mock_env):
136136
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
137137
mock_table.delete_item.assert_not_called()
138138

139-
# Test with different message timestamp and no feedback
139+
# Test with different message timestamp - atomic delete
140140
mock_table.reset_mock()
141141
session_data = {"latest_message_ts": "456"}
142-
with patch("app.slack.slack_events.check_feedback_exists", return_value=False):
143-
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
144-
mock_table.delete_item.assert_called_once_with(Key={"pk": "qa#conv-key#456", "sk": "turn"})
142+
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
143+
mock_table.delete_item.assert_called_once_with(
144+
Key={"pk": "qa#conv-key#456", "sk": "turn"}, ConditionExpression="attribute_not_exists(feedback_received)"
145+
)
145146

146-
# Test with different message timestamp and existing feedback
147+
# Test with ConditionalCheckFailedException (feedback exists)
147148
mock_table.reset_mock()
148-
with patch("app.slack.slack_events.check_feedback_exists", return_value=True):
149-
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
150-
mock_table.delete_item.assert_not_called()
151-
152-
153-
@patch("app.slack.slack_events.table")
154-
def test_check_feedback_exists(mock_table, mock_env):
155-
"""Test check_feedback_exists function"""
156-
from app.slack.slack_events import check_feedback_exists
157-
158-
# Test with existing feedback
159-
mock_table.query.return_value = {"Items": [{"feedback_type": "positive"}]}
160-
result = check_feedback_exists("conv-key", "123")
161-
assert result is True
162-
163-
# Test with no feedback
164-
mock_table.query.return_value = {"Items": []}
165-
result = check_feedback_exists("conv-key", "123")
166-
assert result is False
149+
from botocore.exceptions import ClientError
167150

168-
# Test with exception
169-
mock_table.query.side_effect = Exception("DB error")
170-
result = check_feedback_exists("conv-key", "123")
171-
assert result is False
151+
error = ClientError({"Error": {"Code": "ConditionalCheckFailedException"}}, "DeleteItem")
152+
mock_table.delete_item.side_effect = error
153+
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
154+
mock_table.delete_item.assert_called_once_with(
155+
Key={"pk": "qa#conv-key#456", "sk": "turn"}, ConditionExpression="attribute_not_exists(feedback_received)"
156+
)
172157

173158

174159
@patch("app.slack.slack_events.table")
@@ -223,9 +208,9 @@ def test_cleanup_previous_unfeedback_qa_error_handling(mock_table, mock_env):
223208
from app.slack.slack_events import cleanup_previous_unfeedback_qa
224209

225210
session_data = {"latest_message_ts": "456"}
226-
with patch("app.slack.slack_events.check_feedback_exists", side_effect=Exception("Error")):
227-
# Should not raise exception
228-
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
211+
mock_table.delete_item.side_effect = Exception("DB error")
212+
# Should not raise exception
213+
cleanup_previous_unfeedback_qa("conv-key", "123", session_data)
229214

230215

231216
@patch("app.slack.slack_events.table")

0 commit comments

Comments
 (0)