feat: load update conversations progressively (Closes #761)
This commit is contained in:
1 parent
7c86f28346
commit
a9d586e689
8 files changed
+123
-33
No files matched your search
@@ -284,7 +284,6 @@ async def test_notification_detail_loads_subject_and_latest_comment_for_inbox_re
|
||||
"http://127.0.0.1:3000/api/v1/notifications/threads/42",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/7",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/comments/9",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/7/comments?limit=20&page=1",
|
||||
"http://127.0.0.1:3000/api/v1/repos/stackchain/api/issues/7/subscriptions/check",
|
||||
]
|
||||
assert result == {
|
||||
@@ -304,18 +303,7 @@ async def test_notification_detail_loads_subject_and_latest_comment_for_inbox_re
|
||||
"issue": {"number": 7, "assignees": [], "claimable": True},
|
||||
"acknowledge_supported": True,
|
||||
"mute_supported": True,
|
||||
"conversation": {
|
||||
"comments": [{
|
||||
"id": 9,
|
||||
"author": "alexander",
|
||||
"body": "Logs point to the worker timeout.",
|
||||
"created_at": "2026-08-06T12:30:00Z",
|
||||
"url": "https://forge.example/stackchain/api/issues/7#issuecomment-9",
|
||||
}],
|
||||
"page": 1,
|
||||
"older_page": None,
|
||||
"total": 1,
|
||||
},
|
||||
"conversation_available": True,
|
||||
}
|
||||
|
||||
|
||||
|
||||
+53
-1
@@ -5169,6 +5169,56 @@ reader.open(firstItem).then(async () => {{
|
||||
assert output["states"] == [[41], [90]]
|
||||
|
||||
|
||||
def test_notification_reader_renders_core_before_conversation_and_retries_failure_in_place():
|
||||
script = f"""
|
||||
const build = require({json.dumps(str(MY_WORK))});
|
||||
const createPager = require({json.dumps(str(CONVERSATION))});
|
||||
const events = [];
|
||||
let attempts = 0;
|
||||
let release;
|
||||
const reader = build.createNotificationReader({{
|
||||
load: async id => ({{id, title:'Core ready', conversation_available:true}}),
|
||||
createPager,
|
||||
loadConversation: () => {{
|
||||
attempts += 1;
|
||||
if (attempts === 1) return Promise.reject(new Error('comments offline'));
|
||||
return new Promise(resolve => {{ release = () => resolve({{
|
||||
comments:[{{id:47, body:'Newest'}}], page:3, older_page:2, total:47,
|
||||
}}); }});
|
||||
}},
|
||||
markRead: async () => {{}}, onOpen: () => events.push('open'),
|
||||
onDetail: detail => events.push(['detail', detail.title]),
|
||||
onConversation: state => events.push(['conversation', state.comments.map(item => item.id)]),
|
||||
onConversationStatus: status => events.push(['conversation-status', status]),
|
||||
onItems: () => {{}}, onStatus: status => events.push(['status', status]), onClose: () => {{}},
|
||||
}});
|
||||
(async () => {{
|
||||
const opened = await reader.open({{notification_id:42}});
|
||||
const retrying = reader.retryConversation();
|
||||
await Promise.resolve();
|
||||
const beforeRelease = events.slice();
|
||||
release();
|
||||
const retried = await retrying;
|
||||
process.stdout.write(JSON.stringify({{opened, retried, attempts, beforeRelease, events}}));
|
||||
}})();
|
||||
"""
|
||||
output = json.loads(subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
).stdout)
|
||||
|
||||
assert output["opened"] is True
|
||||
assert output["retried"] is True
|
||||
assert output["attempts"] == 2
|
||||
assert ["detail", "Core ready"] in output["beforeRelease"]
|
||||
assert ["status", "Update ready."] in output["beforeRelease"]
|
||||
assert ["conversation-status", "Conversation temporarily unavailable. Retry."] in output["beforeRelease"]
|
||||
assert ["conversation", [47]] not in output["beforeRelease"]
|
||||
assert output["events"][-2:] == [
|
||||
["conversation", [47]],
|
||||
["conversation-status", "1 of 47 messages loaded."],
|
||||
]
|
||||
|
||||
|
||||
def test_notification_reader_hydrates_saved_conversation_without_server_state_actions():
|
||||
script = f"""
|
||||
const build = require({json.dumps(str(MY_WORK))});
|
||||
@@ -7126,7 +7176,9 @@ async def test_mobile_update_sheet_renders_and_pages_the_complete_conversation()
|
||||
assert 'id="load-older-update-comments"' in update_sheet
|
||||
assert 'id="update-conversation-status"' in update_sheet
|
||||
assert '.conversation-more' in html and 'min-height:44px' in html
|
||||
assert "'/conversation?page='" in html
|
||||
assert "'/conversation?limit=20' + pageQuery" in html
|
||||
assert 'id="retry-update-conversation"' in update_sheet
|
||||
assert "notificationReader.retryConversation()" in html
|
||||
assert "loadConversation: fetchNotificationConversation" in html
|
||||
assert "onConversation: renderUpdateConversation" in html
|
||||
assert "notificationReader.loadOlder()" in html
|
||||
|
||||
@@ -7,7 +7,7 @@ from src import gitea_proxy, main
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_notification_detail_opens_the_newest_conversation_page_in_chronological_order(monkeypatch):
|
||||
async def test_notification_detail_returns_core_without_loading_conversation(monkeypatch):
|
||||
requested_pages = []
|
||||
|
||||
def comment(comment_id):
|
||||
@@ -53,14 +53,9 @@ async def test_notification_detail_opens_the_newest_conversation_page_in_chronol
|
||||
finally:
|
||||
await gitea_proxy.stop_client()
|
||||
|
||||
assert requested_pages == [1, 3]
|
||||
assert [item["id"] for item in result["conversation"]["comments"]] == list(range(41, 48))
|
||||
assert result["conversation"] == {
|
||||
"comments": result["conversation"]["comments"],
|
||||
"page": 3,
|
||||
"older_page": 2,
|
||||
"total": 47,
|
||||
}
|
||||
assert requested_pages == []
|
||||
assert result["conversation_available"] is True
|
||||
assert "conversation" not in result
|
||||
assert result["issue"] == {
|
||||
"number": 7,
|
||||
"assignees": [],
|
||||
@@ -130,6 +125,24 @@ async def test_notification_conversation_api_loads_one_bounded_older_page(monkey
|
||||
assert calls == [(42, 2, 20)]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_notification_conversation_api_opens_newest_page_when_page_is_omitted(monkeypatch):
|
||||
calls = []
|
||||
|
||||
async def conversation(thread_id, page, limit):
|
||||
calls.append((thread_id, page, limit))
|
||||
return {"comments": [{"id": 47}], "page": 3, "older_page": 2, "total": 47}
|
||||
|
||||
monkeypatch.setattr(main.gitea_proxy, "notification_conversation_page", conversation)
|
||||
transport = httpx.ASGITransport(app=main.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get("/api/v1/notifications/42/conversation?limit=20")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["page"] == 3
|
||||
assert calls == [(42, None, 20)]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_notification_detail_timeout_is_sanitized_and_retryable(monkeypatch):
|
||||
async def detail(_thread_id):
|
||||
|
||||
Reference in new issue
Block a user