mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(mobile): report task pages and review state accurately (#36115)
* fix(mobile): report task pages and review state accurately * test(mobile): record task page error provenance * test(mobile): record task page errors in reply matrices
This commit is contained in:
+53
-52
@@ -228,46 +228,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"49c5fd241816": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"50982acf6b28": {
|
||||
"name": "github.countWorkItems#1",
|
||||
"ordinal": 14,
|
||||
@@ -650,6 +610,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"d0a974f0c6a1": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"de7bfe07e04b": {
|
||||
"name": "github.countWorkItems#1",
|
||||
"ordinal": 14,
|
||||
@@ -748,7 +749,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -781,7 +782,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -815,7 +816,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -849,7 +850,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -883,7 +884,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -917,7 +918,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -951,7 +952,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -985,7 +986,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1019,7 +1020,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1053,7 +1054,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1087,7 +1088,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1121,7 +1122,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "1c97db0775ed"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
|
||||
+89
-86
@@ -198,46 +198,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"49c5fd241816": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5e1882223b41": {
|
||||
"name": "linear.listTeams#1",
|
||||
"ordinal": 7,
|
||||
@@ -342,18 +302,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"737f20de5cc0": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 1,
|
||||
"items": [],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {}
|
||||
}
|
||||
},
|
||||
"823a0f3cec86": {
|
||||
"name": "github.listWorkItems#1",
|
||||
"ordinal": 13,
|
||||
@@ -369,6 +317,19 @@
|
||||
"ordinal": 9,
|
||||
"value": ["team-1"]
|
||||
},
|
||||
"925a40556fcf": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 1,
|
||||
"items": [],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {}
|
||||
}
|
||||
},
|
||||
"93695401e4f8": {
|
||||
"name": "linear.listTeams#1",
|
||||
"ordinal": 6,
|
||||
@@ -451,6 +412,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"a9e32280eff6": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {}
|
||||
}
|
||||
},
|
||||
"adcbf91f89c3": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
@@ -566,6 +540,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"d0a974f0c6a1": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"d34ffc174996": {
|
||||
"name": "github.listWorkItems#1",
|
||||
"ordinal": 12,
|
||||
@@ -672,18 +687,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"e4b031c4e9d5": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {}
|
||||
}
|
||||
},
|
||||
"eb79a9b3682a": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
@@ -786,7 +789,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -819,7 +822,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -841,7 +844,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -874,7 +877,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -896,7 +899,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -929,7 +932,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -951,7 +954,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -984,7 +987,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1006,7 +1009,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1039,7 +1042,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1061,7 +1064,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1094,7 +1097,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1116,7 +1119,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1149,7 +1152,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1171,7 +1174,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "e4b031c4e9d5"
|
||||
"github-page-2": "a9e32280eff6"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1204,7 +1207,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "e4b031c4e9d5",
|
||||
"github-page-2": "a9e32280eff6",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1226,7 +1229,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1259,7 +1262,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1281,7 +1284,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0"
|
||||
"github-page-2": "925a40556fcf"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1314,7 +1317,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "737f20de5cc0",
|
||||
"github-page-2": "925a40556fcf",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1336,7 +1339,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "e4b031c4e9d5"
|
||||
"github-page-2": "a9e32280eff6"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1369,7 +1372,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "e4b031c4e9d5",
|
||||
"github-page-2": "a9e32280eff6",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
|
||||
@@ -165,46 +165,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"49c5fd241816": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5e1882223b41": {
|
||||
"name": "linear.listTeams#1",
|
||||
"ordinal": 7,
|
||||
@@ -694,6 +654,47 @@
|
||||
"code": "incompatible_reply"
|
||||
}
|
||||
},
|
||||
"d0a974f0c6a1": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"de1f2f536bd0": {
|
||||
"name": "github.countWorkItems#1",
|
||||
"ordinal": 13,
|
||||
@@ -941,7 +942,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -974,7 +975,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1023,7 +1024,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1050,7 +1051,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1093,7 +1094,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1120,7 +1121,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1163,7 +1164,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1190,7 +1191,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1233,7 +1234,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1260,7 +1261,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1303,7 +1304,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1330,7 +1331,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "cb437d5a4339",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1373,7 +1374,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "32a7c0ae7918",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1400,7 +1401,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "32a7c0ae7918",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1443,7 +1444,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "f3b516f62081",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1470,7 +1471,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "f3b516f62081",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1513,7 +1514,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "b948e8307e81",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1540,7 +1541,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "b948e8307e81",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1583,7 +1584,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "a947768bc0ed",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1610,7 +1611,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "a947768bc0ed",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
@@ -1653,7 +1654,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "c7584e82c72f",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
"effects": ["f57b6504f168", "470893ebd4a7", "5efa1426d38f"]
|
||||
@@ -1680,7 +1681,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "c7584e82c72f",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "bd5c2292b89a",
|
||||
|
||||
@@ -221,46 +221,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"49c5fd241816": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"520f24cdfcf1": {
|
||||
"name": "settings.update#1",
|
||||
"ordinal": 3,
|
||||
@@ -841,6 +801,47 @@
|
||||
"ordinal": 3,
|
||||
"value": false
|
||||
},
|
||||
"d0a974f0c6a1": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"d44ba8747c4c": {
|
||||
"name": "selectedLinearWorkspaceId",
|
||||
"ordinal": 7,
|
||||
@@ -1101,7 +1102,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1134,7 +1135,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1183,7 +1184,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "db17b820bb60",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1198,7 +1199,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "db17b820bb60",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1241,7 +1242,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "db17b820bb60",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1256,7 +1257,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "db17b820bb60",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1311,7 +1312,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": [
|
||||
@@ -1332,7 +1333,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1393,7 +1394,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": [
|
||||
@@ -1414,7 +1415,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1475,7 +1476,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": [
|
||||
@@ -1496,7 +1497,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1545,7 +1546,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "32a7c0ae7918",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1560,7 +1561,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "32a7c0ae7918",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1603,7 +1604,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "f3b516f62081",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1618,7 +1619,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "f3b516f62081",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1661,7 +1662,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "b948e8307e81",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1676,7 +1677,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "b948e8307e81",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1719,7 +1720,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "a947768bc0ed",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1734,7 +1735,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "a947768bc0ed",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
@@ -1777,7 +1778,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "c7584e82c72f",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
"effects": []
|
||||
@@ -1792,7 +1793,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "c7584e82c72f",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "0577812165ed",
|
||||
|
||||
@@ -186,46 +186,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"49c5fd241816": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"534fb96c7e7f": {
|
||||
"name": "settings.update#1",
|
||||
"ordinal": 10,
|
||||
@@ -567,6 +527,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"d0a974f0c6a1": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"d6a6916c7399": {
|
||||
"name": "settings.update#1",
|
||||
"ordinal": 10,
|
||||
@@ -732,7 +733,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -765,7 +766,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -807,7 +808,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -840,7 +841,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -882,7 +883,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -915,7 +916,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -957,7 +958,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -990,7 +991,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1032,7 +1033,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1065,7 +1066,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1107,7 +1108,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1140,7 +1141,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1182,7 +1183,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1215,7 +1216,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1257,7 +1258,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1290,7 +1291,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1332,7 +1333,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1365,7 +1366,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1407,7 +1408,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1440,7 +1441,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
@@ -1482,7 +1483,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -1515,7 +1516,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
|
||||
@@ -85,46 +85,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"49c5fd241816": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5e1882223b41": {
|
||||
"name": "linear.listTeams#1",
|
||||
"ordinal": 7,
|
||||
@@ -294,6 +254,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"d0a974f0c6a1": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"errorTypes": [],
|
||||
"failedCount": 0,
|
||||
"items": [
|
||||
{
|
||||
"key": "github:repo-1:issue:9",
|
||||
"provider": "github",
|
||||
"source": {
|
||||
"author": {
|
||||
"$rpc": "null"
|
||||
},
|
||||
"id": "issue:9",
|
||||
"labels": [],
|
||||
"number": 9,
|
||||
"repoId": "repo-1",
|
||||
"repoName": "Repo",
|
||||
"state": "open",
|
||||
"title": "An issue",
|
||||
"type": "issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||
"url": ""
|
||||
},
|
||||
"status": "Open",
|
||||
"subtitle": "Repo #9",
|
||||
"title": "An issue",
|
||||
"updatedAt": "2020-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"sourceErrors": [],
|
||||
"sourceFallbacks": [],
|
||||
"sourcesByRepoId": {
|
||||
"repo-1": {
|
||||
"issues": "upstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"de7bfe07e04b": {
|
||||
"name": "github.countWorkItems#1",
|
||||
"ordinal": 14,
|
||||
@@ -392,7 +393,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816"
|
||||
"github-page-2": "d0a974f0c6a1"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
"effects": [
|
||||
@@ -425,7 +426,7 @@
|
||||
"mount": "eb79a9b3682a",
|
||||
"linear-context-0": "eb79a9b3682a",
|
||||
"persist-teams-1": "eb79a9b3682a",
|
||||
"github-page-2": "49c5fd241816",
|
||||
"github-page-2": "d0a974f0c6a1",
|
||||
"github-count-3": "adcbf91f89c3"
|
||||
},
|
||||
"state": "67b5ebc67646",
|
||||
|
||||
@@ -3869,6 +3869,21 @@
|
||||
},
|
||||
"worktreeCreateRetry": {
|
||||
"createFailed": "Failed to create workspace"
|
||||
},
|
||||
"hostedMerge": {
|
||||
"merged": "Merged",
|
||||
"closed": "Closed",
|
||||
"conflicts": "Conflicts",
|
||||
"behind": "Behind",
|
||||
"blocked": "Blocked",
|
||||
"checksPending": "Checks pending",
|
||||
"ableToMerge": "Able to merge",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"pagination": {
|
||||
"pageUnreachable": "Page {{page}} is beyond what GitHub search can return.",
|
||||
"loadPageFailed": "Page {{page}} could not be loaded from GitHub.",
|
||||
"noMoreResults": "No more results on page {{page}}."
|
||||
}
|
||||
},
|
||||
"terminal": {
|
||||
|
||||
@@ -2,11 +2,35 @@ import { describe, expect, it } from 'vitest'
|
||||
import type { HostedReviewDecision } from '../../../src/shared/hosted-review'
|
||||
import {
|
||||
getHostedChecksLabel,
|
||||
getHostedMergeLabel,
|
||||
getHostedReviewLabel,
|
||||
getHostedReviewSignalTone
|
||||
} from './mobile-hosted-check-status'
|
||||
|
||||
describe('mobile hosted check status', () => {
|
||||
it.each(['merged', 'closed'] as const)('shows %s before stale merge enrichment', (state) => {
|
||||
expect(getHostedMergeLabel({ state, mergeable: 'CONFLICTING' })).toBe(
|
||||
state === 'merged' ? 'Merged' : 'Closed'
|
||||
)
|
||||
expect(getHostedReviewSignalTone({ state, mergeable: 'CONFLICTING' }, 'merge')).toBe('neutral')
|
||||
})
|
||||
|
||||
it('does not call pending checks Behind', () => {
|
||||
const item = {
|
||||
mergeable: 'MERGEABLE' as const,
|
||||
checksSummary: {
|
||||
state: 'pending' as const,
|
||||
total: 1,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
pending: 1,
|
||||
neutral: 0
|
||||
}
|
||||
}
|
||||
expect(getHostedMergeLabel(item)).toBe('Checks pending')
|
||||
expect(getHostedReviewSignalTone(item, 'merge')).toBe('warning')
|
||||
})
|
||||
|
||||
it('renders a hydrated neutral GitLab summary as unresolved checks', () => {
|
||||
const summary = {
|
||||
state: 'neutral' as const,
|
||||
|
||||
@@ -2,9 +2,12 @@ import type {
|
||||
PRMergeableState,
|
||||
ProviderCheckSummary
|
||||
} from '../../../src/shared/github/pull-request-types'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import { resolveGitHubPRMergeState } from '../../../src/shared/github/pull-request-merge-state'
|
||||
import { getProviderChecksLabel } from '../../../src/shared/provider-check-summary'
|
||||
|
||||
export type MobileHostedReviewStatus = {
|
||||
state?: string
|
||||
checksSummary?: ProviderCheckSummary
|
||||
reviewDecision?: string | null
|
||||
reviewRequests?: readonly unknown[]
|
||||
@@ -29,17 +32,47 @@ export function getHostedReviewLabel(item: MobileHostedReviewStatus): string {
|
||||
: 'No reviewers'
|
||||
}
|
||||
|
||||
export function getHostedMergeLabel(item: MobileHostedReviewStatus): string {
|
||||
if (item.mergeable === 'CONFLICTING' || item.mergeStateStatus === 'BLOCKED') {
|
||||
return 'Conflicts'
|
||||
function hostedMergeState(item: MobileHostedReviewStatus) {
|
||||
return resolveGitHubPRMergeState({
|
||||
state:
|
||||
item.state === 'merged' || item.state === 'closed' || item.state === 'draft'
|
||||
? item.state
|
||||
: 'open',
|
||||
mergeable: item.mergeable,
|
||||
mergeStateStatus: item.mergeStateStatus,
|
||||
checksSummary: item.checksSummary
|
||||
}).kind
|
||||
}
|
||||
|
||||
export function getHostedMergeLabel(item: MobileHostedReviewStatus): string | null {
|
||||
switch (hostedMergeState(item)) {
|
||||
case 'merged':
|
||||
return translate('tasks.hostedMerge.merged', 'Merged')
|
||||
case 'closed':
|
||||
return translate('tasks.hostedMerge.closed', 'Closed')
|
||||
case 'conflicts':
|
||||
return translate('tasks.hostedMerge.conflicts', 'Conflicts')
|
||||
case 'behind':
|
||||
return translate('tasks.hostedMerge.behind', 'Behind')
|
||||
case 'blocked':
|
||||
return translate('tasks.hostedMerge.blocked', 'Blocked')
|
||||
case 'mergeable_checks_pending':
|
||||
return translate('tasks.hostedMerge.checksPending', 'Checks pending')
|
||||
case 'mergeable':
|
||||
case 'mergeable_checks_failed':
|
||||
case 'mergeable_checks_passed':
|
||||
return translate('tasks.hostedMerge.ableToMerge', 'Able to merge')
|
||||
case 'merge_status_unavailable':
|
||||
case 'checks_passed':
|
||||
case 'draft':
|
||||
return null
|
||||
case 'review_required':
|
||||
case 'changes_requested':
|
||||
case 'merge_queue':
|
||||
case 'merge_queue_auto_merge_on':
|
||||
case 'checking':
|
||||
return translate('tasks.hostedMerge.unknown', 'Unknown')
|
||||
}
|
||||
if (item.mergeStateStatus === 'BEHIND' || item.checksSummary?.state === 'pending') {
|
||||
return 'Behind'
|
||||
}
|
||||
if (item.mergeable === 'MERGEABLE' || item.mergeStateStatus === 'CLEAN') {
|
||||
return 'Able to merge'
|
||||
}
|
||||
return 'Unknown'
|
||||
}
|
||||
|
||||
export function getHostedChecksLabel(item: { checksSummary?: ProviderCheckSummary }): string {
|
||||
@@ -82,14 +115,27 @@ export function getHostedReviewSignalTone(
|
||||
}
|
||||
return 'neutral'
|
||||
}
|
||||
if (item.mergeable === 'CONFLICTING' || item.mergeStateStatus === 'BLOCKED') {
|
||||
return 'danger'
|
||||
switch (hostedMergeState(item)) {
|
||||
case 'conflicts':
|
||||
case 'blocked':
|
||||
return 'danger'
|
||||
case 'behind':
|
||||
case 'mergeable_checks_pending':
|
||||
return 'warning'
|
||||
case 'mergeable':
|
||||
case 'mergeable_checks_failed':
|
||||
case 'mergeable_checks_passed':
|
||||
return 'success'
|
||||
case 'merged':
|
||||
case 'closed':
|
||||
case 'draft':
|
||||
case 'review_required':
|
||||
case 'changes_requested':
|
||||
case 'merge_queue':
|
||||
case 'merge_queue_auto_merge_on':
|
||||
case 'merge_status_unavailable':
|
||||
case 'checks_passed':
|
||||
case 'checking':
|
||||
return 'neutral'
|
||||
}
|
||||
if (item.mergeStateStatus === 'BEHIND' || item.checksSummary?.state === 'pending') {
|
||||
return 'warning'
|
||||
}
|
||||
if (item.mergeable === 'MERGEABLE' || item.mergeStateStatus === 'CLEAN') {
|
||||
return 'success'
|
||||
}
|
||||
return 'neutral'
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ afterEach(() => {
|
||||
mounted.clear()
|
||||
})
|
||||
|
||||
function reply(request: SentRequest): RpcResponse {
|
||||
function reply(request: SentRequest, rowCount = 1): RpcResponse {
|
||||
if (request.method === 'github.countWorkItems') {
|
||||
return { id: 'count', ok: true, result: { count: 100 } }
|
||||
return { id: 'count', ok: true, result: 100 }
|
||||
}
|
||||
const params = request.args[1]
|
||||
const query =
|
||||
@@ -51,19 +51,17 @@ function reply(request: SentRequest): RpcResponse {
|
||||
id: 'list',
|
||||
ok: true,
|
||||
result: {
|
||||
items: [
|
||||
{
|
||||
id: 'issue-1',
|
||||
type: 'issue',
|
||||
number: 1,
|
||||
title: query,
|
||||
state: 'open',
|
||||
labels: [],
|
||||
url: 'https://example.com/issues/1',
|
||||
updatedAt: '2026-10-27T00:00:00Z',
|
||||
author: null
|
||||
}
|
||||
]
|
||||
items: Array.from({ length: rowCount }, (_, index) => ({
|
||||
id: `issue-${index}`,
|
||||
type: 'issue',
|
||||
number: index + 1,
|
||||
title: query,
|
||||
state: 'open',
|
||||
labels: [],
|
||||
url: 'https://example.com/issues/1',
|
||||
updatedAt: '2026-10-27T00:00:00Z',
|
||||
author: null
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,11 +182,11 @@ async function mount(provider: Provider, repoCount = 30, counted = false) {
|
||||
return { request: request! }
|
||||
},
|
||||
pending,
|
||||
async answer(requests = pending()) {
|
||||
async answer(requests = pending(), respond = reply) {
|
||||
await act(async () => {
|
||||
for (const request of requests) {
|
||||
answered.add(request)
|
||||
request.resolve(reply(request))
|
||||
request.resolve(respond(request))
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -312,3 +310,77 @@ describe('task repository request ownership', () => {
|
||||
expect(fixture.model().setRefreshing).toHaveBeenLastCalledWith(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GitHub next-page source failures', () => {
|
||||
for (const [source, type, message] of [
|
||||
['prs', 'rate_limited', 'API rate limit exceeded'],
|
||||
['issues', 'permission_denied', 'Resource not accessible'],
|
||||
['issues', 'validation_error', 'Invalid query'],
|
||||
['prs', 'validation_error', 'Only the first 1000 search results are available']
|
||||
] as const) {
|
||||
it(`keeps Next retryable for ${source} ${type}`, async () => {
|
||||
const fixture = await mount('github', 1)
|
||||
const first = await fixture.load()
|
||||
await fixture.answer(fixture.pending(), (request) => reply(request, 72))
|
||||
await first.request
|
||||
expect(fixture.model().githubCanLoadUncountedNextPage).toBe(true)
|
||||
let page: Promise<void> | undefined
|
||||
await act(async () => {
|
||||
page = fixture.model().handleGitHubPageChange(1)
|
||||
})
|
||||
await fixture.answer(fixture.pending(), () => ({
|
||||
id: 'page',
|
||||
ok: true,
|
||||
result: { items: [], errors: { [source]: { type, message } } }
|
||||
}))
|
||||
await page
|
||||
expect(fixture.model().setError).toHaveBeenLastCalledWith(
|
||||
'Page 2 could not be loaded from GitHub.'
|
||||
)
|
||||
expect(fixture.model().githubCanLoadUncountedNextPage).toBe(true)
|
||||
expect(fixture.model().githubCountedTotalPages).toBeNull()
|
||||
expect(fixture.model().githubPages).toHaveLength(1)
|
||||
})
|
||||
}
|
||||
|
||||
it('keeps a proven issue-search window limit through a late count, resetting on reload', async () => {
|
||||
const fixture = await mount('github', 1, true)
|
||||
const first = await fixture.load()
|
||||
await fixture.answer(fixture.pending(), (request) => reply(request, 72))
|
||||
await first.request
|
||||
const count = fixture.pending()
|
||||
expect(starts(count, 'github.countWorkItems')).toHaveLength(1)
|
||||
let page: Promise<void> | undefined
|
||||
await act(async () => {
|
||||
page = fixture.model().handleGitHubPageChange(1)
|
||||
})
|
||||
await fixture.answer(
|
||||
fixture.pending().filter((request) => !count.includes(request)),
|
||||
() => ({
|
||||
id: 'page',
|
||||
ok: true,
|
||||
result: {
|
||||
items: [],
|
||||
errors: {
|
||||
issues: {
|
||||
type: 'validation_error',
|
||||
message: 'Only the first 1000 search results are available'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
await page
|
||||
expect(fixture.model().setError).toHaveBeenLastCalledWith(
|
||||
'Page 2 is beyond what GitHub search can return.'
|
||||
)
|
||||
expect(fixture.model().githubCanLoadUncountedNextPage).toBe(false)
|
||||
await fixture.answer(count)
|
||||
expect(fixture.model().githubCountedTotalPages).toBe(3)
|
||||
expect(fixture.model().githubTotalPages).toBe(1)
|
||||
const fresh = await fixture.load()
|
||||
await fixture.drain()
|
||||
await fresh.request
|
||||
expect(fixture.model().githubTotalPages).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { getGitHubMergeLabel } from './mobile-tasks-hosted-review'
|
||||
import type { GitHubWorkItem } from './mobile-tasks-provider-detail-types'
|
||||
|
||||
vi.mock('./mobile-tasks-item-mapping', () => ({ projectRowType: vi.fn() }))
|
||||
|
||||
const item: GitHubWorkItem = {
|
||||
id: 'pr:30140',
|
||||
type: 'pr',
|
||||
number: 30140,
|
||||
title: 'Page GitHub tasks by number',
|
||||
state: 'merged',
|
||||
url: 'https://github.com/stablyai/orca/pull/30140',
|
||||
labels: [],
|
||||
updatedAt: '2026-10-16T00:00:00Z',
|
||||
author: null,
|
||||
repoId: 'orca',
|
||||
repoName: 'orca'
|
||||
}
|
||||
|
||||
describe('mobile Tasks GitHub merge label', () => {
|
||||
it.each(['merged', 'closed'] as const)('uses %s state without merge enrichment', (state) => {
|
||||
expect(getGitHubMergeLabel({ ...item, state })).toBe(state === 'merged' ? 'Merged' : 'Closed')
|
||||
})
|
||||
|
||||
it('hides the merge label for open list rows without merge enrichment', () => {
|
||||
expect(getGitHubMergeLabel({ ...item, state: 'open' })).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getHostedMergeLabel } from './mobile-hosted-check-status'
|
||||
import { projectRowType } from './mobile-tasks-item-mapping'
|
||||
import type {
|
||||
HostedReviewItem,
|
||||
@@ -131,29 +132,8 @@ export function hostedBranchSummary(item: TaskItem): { head: string; base: strin
|
||||
return null
|
||||
}
|
||||
|
||||
export function getGitHubMergeLabel(item: GitHubWorkItem): string {
|
||||
if (item.mergeable === undefined && item.mergeStateStatus === undefined) {
|
||||
return 'Merge'
|
||||
}
|
||||
if (item.state === 'merged') {
|
||||
return 'Merged'
|
||||
}
|
||||
if (item.state === 'closed') {
|
||||
return 'Closed'
|
||||
}
|
||||
if (item.mergeable === 'CONFLICTING') {
|
||||
return 'Conflicts'
|
||||
}
|
||||
if (item.mergeStateStatus === 'BEHIND') {
|
||||
return 'Behind'
|
||||
}
|
||||
if (item.mergeStateStatus === 'BLOCKED') {
|
||||
return 'Blocked'
|
||||
}
|
||||
if (item.mergeable === 'MERGEABLE' || item.mergeStateStatus === 'CLEAN') {
|
||||
return 'Able to merge'
|
||||
}
|
||||
return 'Unknown'
|
||||
export function getGitHubMergeLabel(item: GitHubWorkItem): string | null {
|
||||
return getHostedMergeLabel(item)
|
||||
}
|
||||
|
||||
export function getHostedReviewMergeMethodLabel(method: HostedReviewMergeMethod): string {
|
||||
|
||||
@@ -178,6 +178,11 @@ export function renderMobileTasksProviderItemList(
|
||||
const isGitLabMr = item.provider === 'gitlab' && item.source.type === 'mr'
|
||||
const githubPrDelta = isGitHubPr ? formatGitHubPRDelta(item.source) : null
|
||||
const branchSummary = hostedBranchSummary(item)
|
||||
const mergeLabel = isGitHubPr
|
||||
? getGitHubMergeLabel(item.source)
|
||||
: isGitLabMr
|
||||
? getHostedMergeLabel(item.source)
|
||||
: null
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.taskRow, pressed && styles.taskRowPressed]}
|
||||
@@ -256,7 +261,7 @@ export function renderMobileTasksProviderItemList(
|
||||
{getHostedChecksLabel(item.source)}
|
||||
</Text>
|
||||
</View>
|
||||
{isGitHubPr || isGitLabMr ? (
|
||||
{mergeLabel ? (
|
||||
<View
|
||||
style={[
|
||||
styles.prSignalChip,
|
||||
@@ -264,9 +269,7 @@ export function renderMobileTasksProviderItemList(
|
||||
]}
|
||||
>
|
||||
<Text style={styles.prSignalText} numberOfLines={1}>
|
||||
{isGitHubPr
|
||||
? getGitHubMergeLabel(item.source)
|
||||
: getHostedMergeLabel(item.source)}
|
||||
{mergeLabel}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ClassifiedError } from '../../../src/shared/classified-error'
|
||||
import { getWorkItemPageErrorTypes } from '../../../src/shared/github/work-item-page-errors'
|
||||
import type { RuntimeHydrationModel } from './use-mobile-tasks-runtime-hydration'
|
||||
import { readJiraConnection, reconcileJiraConnection } from './jira-mobile-connection'
|
||||
import {
|
||||
@@ -138,6 +140,7 @@ export function useMobileTasksProviderLoadActions<Model extends ProviderLoadActi
|
||||
): Promise<{
|
||||
items: Array<Extract<TaskItem, { provider: 'github' }>>
|
||||
failedCount: number
|
||||
errorTypes: ClassifiedError['type'][]
|
||||
sourcesByRepoId: Record<string, GitHubRepoSources>
|
||||
sourceErrors: GitHubIssueSourceError[]
|
||||
sourceFallbacks: GitHubIssueSourceFallback[]
|
||||
@@ -161,8 +164,9 @@ export function useMobileTasksProviderLoadActions<Model extends ProviderLoadActi
|
||||
query: scopeGitHubTaskSearch(appliedQuery, githubKind),
|
||||
page: taskPageToGitHubApiPage(taskPage)
|
||||
})
|
||||
const decoded = githubWorkItemSearchRead.interpret(reply)
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the schema requires `items` and each row's `labels`, and types the other eight (`id`, `type`, `number`, `title`, `state`, `url`, `updatedAt`, `author`). Two gaps keep the cast: the salvaged members type as `T | undefined` where GitHubWorkItem declares them required, and `sources`/`errors`/`issueSourceFellBack` are deliberately `z.unknown()` because the two banner extractors below read them member by member with their own guards.
|
||||
const envelope = githubWorkItemSearchRead.interpret(reply) as {
|
||||
const envelope = decoded as {
|
||||
items: Array<Omit<GitHubWorkItem, 'repoId' | 'repoName'>>
|
||||
sources?: GitHubRepoSources
|
||||
errors?: { issues?: { message: string } }
|
||||
@@ -171,6 +175,7 @@ export function useMobileTasksProviderLoadActions<Model extends ProviderLoadActi
|
||||
return {
|
||||
items: envelope.items.map((item) => createGitHubTask(repo, item)),
|
||||
sources: envelope.sources,
|
||||
errorTypes: getWorkItemPageErrorTypes(decoded.errors),
|
||||
sourceError: extractGitHubIssueSourceError(repo, envelope),
|
||||
sourceFallback: extractGitHubIssueSourceFallback(repo, envelope),
|
||||
repoId: repo.id
|
||||
@@ -213,6 +218,7 @@ export function useMobileTasksProviderLoadActions<Model extends ProviderLoadActi
|
||||
.flatMap((result) => result.items)
|
||||
.sort((a, b) => b.source.number - a.source.number),
|
||||
failedCount: results.filter((result) => result.error).length,
|
||||
errorTypes: results.flatMap((result) => result.errorTypes ?? []),
|
||||
sourcesByRepoId,
|
||||
sourceErrors,
|
||||
sourceFallbacks
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createElement } from 'react'
|
||||
import { createElement, useState } from 'react'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
@@ -7,6 +7,7 @@ vi.mock('./mobile-tasks-dependencies', async () => {
|
||||
return {
|
||||
useCallback: react.useCallback,
|
||||
useMemo: react.useMemo,
|
||||
useState: react.useState,
|
||||
...(await import('../../../src/shared/work-items'))
|
||||
}
|
||||
})
|
||||
@@ -43,6 +44,7 @@ function buildModel(
|
||||
taskUiReady: true,
|
||||
setError: vi.fn(),
|
||||
setGithubCurrentPage: vi.fn(),
|
||||
setGithubCountedTotalPages: vi.fn(),
|
||||
setGithubLoadingTargetPage: vi.fn(),
|
||||
setGithubPages: vi.fn(),
|
||||
setGithubPaginationLoading: vi.fn(),
|
||||
@@ -53,15 +55,19 @@ function buildModel(
|
||||
let renderer: ReactTestRenderer | null = null
|
||||
let changePage: ((page: number) => Promise<void>) | null = null
|
||||
let canLoadUncountedNextPage = false
|
||||
let totalPages = 0
|
||||
|
||||
function mount(model: ReturnType<typeof buildModel>): void {
|
||||
function Harness(): null {
|
||||
const [countedTotalPages, setCountedTotalPages] = useState(model.githubCountedTotalPages)
|
||||
model.setGithubCountedTotalPages.mockImplementation(setCountedTotalPages)
|
||||
const actions = useMobileTasksTaskPaginationActions(
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the fixture carries every member the page handler reads.
|
||||
model as unknown as TaskListLoadingModel
|
||||
{ ...model, githubCountedTotalPages: countedTotalPages } as unknown as TaskListLoadingModel
|
||||
)
|
||||
changePage = actions.handleGitHubPageChange
|
||||
canLoadUncountedNextPage = actions.githubCanLoadUncountedNextPage
|
||||
totalPages = actions.githubTotalPages
|
||||
return null
|
||||
}
|
||||
act(() => {
|
||||
@@ -79,7 +85,8 @@ describe('GitHub task page change', () => {
|
||||
it('asks for the target page by number and shows it', async () => {
|
||||
const model = buildModel(async () => ({
|
||||
items: oldSelectionPage,
|
||||
failedCount: 0
|
||||
failedCount: 0,
|
||||
errorTypes: []
|
||||
}))
|
||||
mount(model)
|
||||
await act(async () => {
|
||||
@@ -110,7 +117,7 @@ describe('GitHub task page change', () => {
|
||||
})
|
||||
model.loadGenerationRef.current += 1
|
||||
await act(async () => {
|
||||
land({ items: oldSelectionPage, failedCount: 0 })
|
||||
land({ items: oldSelectionPage, failedCount: 0, errorTypes: [] })
|
||||
await pending
|
||||
})
|
||||
expect(model.setGithubPages).not.toHaveBeenCalled()
|
||||
@@ -118,6 +125,46 @@ describe('GitHub task page change', () => {
|
||||
expect(model.setGithubPaginationLoading).toHaveBeenLastCalledWith(false)
|
||||
})
|
||||
|
||||
it('explains a clean empty next page and withdraws speculative Next', async () => {
|
||||
const model = buildModel(async () => ({ items: [], failedCount: 0, errorTypes: [] }), null)
|
||||
model.githubPages = [
|
||||
Array.from({ length: 72 }, (_, index) => ({
|
||||
key: `github:repo-1:issue:${index}`,
|
||||
source: { repoId: 'repo-1' }
|
||||
}))
|
||||
]
|
||||
mount(model)
|
||||
expect(canLoadUncountedNextPage).toBe(true)
|
||||
await act(async () => {
|
||||
await changePage?.(1)
|
||||
})
|
||||
expect(model.setError).toHaveBeenCalledWith('No more results on page 2.')
|
||||
expect(canLoadUncountedNextPage).toBe(false)
|
||||
expect(model.setItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('explains an all-repo failure and keeps the page retryable', async () => {
|
||||
const model = buildModel(async () => ({ items: [], failedCount: 1, errorTypes: [] }))
|
||||
mount(model)
|
||||
await act(async () => {
|
||||
await changePage?.(1)
|
||||
})
|
||||
expect(model.setError).toHaveBeenCalledWith('Page 2 could not be loaded from GitHub.')
|
||||
expect(model.setGithubCountedTotalPages).not.toHaveBeenCalled()
|
||||
expect(model.setItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps a real count when an empty page is returned', async () => {
|
||||
const model = buildModel(async () => ({ items: [], failedCount: 0, errorTypes: [] }), 3)
|
||||
mount(model)
|
||||
await act(async () => {
|
||||
await changePage?.(1)
|
||||
})
|
||||
expect(model.setError).toHaveBeenCalledWith('No more results on page 2.')
|
||||
expect(totalPages).toBe(3)
|
||||
expect(model.setItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('without a count, as in the default all-repos view', () => {
|
||||
function rows(repoId: string, count: number): PageRow[] {
|
||||
return Array.from({ length: count }, (_, index) => ({
|
||||
@@ -127,7 +174,7 @@ describe('GitHub task page change', () => {
|
||||
}
|
||||
|
||||
function lastPageOffersNext(page: PageRow[]): boolean {
|
||||
const model = buildModel(async () => ({ items: [], failedCount: 0 }), null)
|
||||
const model = buildModel(async () => ({ items: [], failedCount: 0, errorTypes: [] }), null)
|
||||
model.githubPages = [page]
|
||||
model.selectedHostedRepos = [{ id: 'repo-a' }, { id: 'repo-b' }]
|
||||
mount(model)
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import {
|
||||
applyEmptyPageClamp,
|
||||
applyWindowPageLimit,
|
||||
deriveAdvertisedTotalPages,
|
||||
resolveEmptyPageOutcome
|
||||
} from '../../../src/shared/work-item-empty-page'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import type { TaskListLoadingModel } from './use-mobile-tasks-task-list-loading'
|
||||
import {
|
||||
CROSS_REPO_DISPLAY_LIMIT,
|
||||
PER_REPO_FETCH_LIMIT,
|
||||
getTaskPagePerRepoLimit,
|
||||
useCallback,
|
||||
useMemo
|
||||
useMemo,
|
||||
useState
|
||||
} from './mobile-tasks-dependencies'
|
||||
import { type TaskItem, buildPartialRepositoryNotice } from './mobile-tasks-legacy-foundation'
|
||||
import { linearAccountConnect } from './mobile-task-list-operations'
|
||||
@@ -42,6 +50,7 @@ export function useMobileTasksTaskPaginationActions(model: TaskListLoadingModel)
|
||||
selectedHostedRepos,
|
||||
setError,
|
||||
setGithubCurrentPage,
|
||||
setGithubCountedTotalPages,
|
||||
setGithubLoadingTargetPage,
|
||||
setGithubPages,
|
||||
setGithubPaginationLoading,
|
||||
@@ -109,10 +118,15 @@ export function useMobileTasksTaskPaginationActions(model: TaskListLoadingModel)
|
||||
[loadTasks]
|
||||
)
|
||||
|
||||
// Zero count uses the desktop’s failed-count fallback.
|
||||
const githubTotalPages = githubCountedTotalPages
|
||||
? Math.max(githubPages.length, githubCountedTotalPages)
|
||||
: githubPages.length
|
||||
const [windowLimit, setWindowLimit] = useState<{ generation: number; pages: number } | null>(null)
|
||||
const provenPageLimit =
|
||||
windowLimit?.generation === loadGenerationRef.current ? windowLimit.pages : null
|
||||
const githubTotalPages = deriveAdvertisedTotalPages({
|
||||
loadedPages: githubPages.length,
|
||||
countedTotalPages: githubCountedTotalPages,
|
||||
fallbackTotalPages: githubPages.length,
|
||||
provenPageLimit
|
||||
})
|
||||
const githubPerRepoLimit = getTaskPagePerRepoLimit(
|
||||
selectedHostedRepos.length,
|
||||
PER_REPO_FETCH_LIMIT,
|
||||
@@ -120,7 +134,9 @@ export function useMobileTasksTaskPaginationActions(model: TaskListLoadingModel)
|
||||
)
|
||||
// One full per-repo page may have more; all-repo mode skips counts and relies on this to reach page two.
|
||||
const githubCanLoadUncountedNextPage =
|
||||
!githubCountedTotalPages && hasRepoWithFullPage(githubPages.at(-1) ?? [], githubPerRepoLimit)
|
||||
!githubCountedTotalPages &&
|
||||
(provenPageLimit === null || githubPages.length < provenPageLimit) &&
|
||||
hasRepoWithFullPage(githubPages.at(-1) ?? [], githubPerRepoLimit)
|
||||
const githubCanShowPagination =
|
||||
githubTotalPages > 1 || (githubPages.length > 0 && githubCanLoadUncountedNextPage)
|
||||
const githubPagePickerPages = useMemo(() => {
|
||||
@@ -165,11 +181,52 @@ export function useMobileTasksTaskPaginationActions(model: TaskListLoadingModel)
|
||||
const generation = loadGenerationRef.current
|
||||
const isCurrent = () =>
|
||||
loadGenerationRef.current === generation && clientRef.current === client
|
||||
setError('')
|
||||
setGithubPaginationLoading(true)
|
||||
setGithubLoadingTargetPage(targetPage)
|
||||
try {
|
||||
const page = await fetchGitHubItemsPage(client, selectedHostedRepos, targetPage, isCurrent)
|
||||
if (!isCurrent() || page.items.length === 0) {
|
||||
if (!isCurrent()) {
|
||||
return
|
||||
}
|
||||
if (page.items.length === 0) {
|
||||
const emptyPage = {
|
||||
target: targetPage,
|
||||
failedCount: page.failedCount,
|
||||
errorTypes: page.errorTypes
|
||||
}
|
||||
const { reason } = resolveEmptyPageOutcome({ ...emptyPage, countedTotalPages: null })
|
||||
if (reason === 'window-unreachable') {
|
||||
setError(
|
||||
translate(
|
||||
'tasks.pagination.pageUnreachable',
|
||||
'Page {{page}} is beyond what GitHub search can return.',
|
||||
{ page: targetPage + 1 }
|
||||
)
|
||||
)
|
||||
setWindowLimit((previous) => ({
|
||||
generation,
|
||||
pages: applyWindowPageLimit(
|
||||
previous?.generation === generation ? previous.pages : null,
|
||||
targetPage
|
||||
)
|
||||
}))
|
||||
} else if (reason === 'load-failed') {
|
||||
setError(
|
||||
translate(
|
||||
'tasks.pagination.loadPageFailed',
|
||||
'Page {{page}} could not be loaded from GitHub.',
|
||||
{ page: targetPage + 1 }
|
||||
)
|
||||
)
|
||||
} else {
|
||||
setError(
|
||||
translate('tasks.pagination.noMoreResults', 'No more results on page {{page}}.', {
|
||||
page: targetPage + 1
|
||||
})
|
||||
)
|
||||
setGithubCountedTotalPages((previous) => applyEmptyPageClamp(previous, emptyPage))
|
||||
}
|
||||
return
|
||||
}
|
||||
if (page.failedCount > 0) {
|
||||
@@ -195,6 +252,7 @@ export function useMobileTasksTaskPaginationActions(model: TaskListLoadingModel)
|
||||
githubPaginationLoading,
|
||||
loadGenerationRef,
|
||||
selectedHostedRepos,
|
||||
setGithubCountedTotalPages,
|
||||
tasksSupported
|
||||
]
|
||||
)
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import {
|
||||
} from '@/components/task-page/github/task-page-github-work-item-mutation-pages'
|
||||
import { clearTaskPageGitHubAuthorityThroughGeneration } from '@/components/task-page/github/task-page-github-work-item-authority-refresh'
|
||||
import { getOrCreateQuietRevalidateState } from '@/components/task-page/github/task-page-github-work-item-quiet-state'
|
||||
import { resolveEmptyPageOutcome } from '@/components/task-page/task-page-work-item-pagination'
|
||||
import { resolveEmptyPageOutcome } from '../../../../../shared/work-item-empty-page'
|
||||
import {
|
||||
deriveTaskPageGitHubWorkItemsFetchOptions,
|
||||
shouldReplaceTaskPageItemsAfterRefresh,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { useCallback, useMemo, useEffect } from 'react'
|
||||
import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types'
|
||||
import { taskPageGitHubItemKey } from '@/components/task-page/github/task-page-github-work-item-mutation-keys'
|
||||
import { deriveTaskPagePRCheckSummary } from '../task-page-pr-check-summary'
|
||||
import { deriveAdvertisedTotalPages } from '@/components/task-page/task-page-work-item-pagination'
|
||||
import { deriveAdvertisedTotalPages } from '../../../../../shared/work-item-empty-page'
|
||||
import {
|
||||
GITHUB_PR_TASK_GRID_CLASS,
|
||||
GITHUB_TASK_GRID_CLASS,
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ import { stripRepoQualifiers } from '../../../../../shared/task-query'
|
||||
import { taskPageToGitHubApiPage } from '../../../../../shared/work-items'
|
||||
import {
|
||||
resolveEmptyPageOutcome,
|
||||
applyWindowPageLimit,
|
||||
applyEmptyPageClamp
|
||||
} from '@/components/task-page/task-page-work-item-pagination'
|
||||
applyEmptyPageClamp,
|
||||
applyWindowPageLimit
|
||||
} from '../../../../../shared/work-item-empty-page'
|
||||
import { toast } from 'sonner'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { overlayPendingOnTaskPagePages } from '@/components/task-page/github/task-page-github-work-item-mutation-pages'
|
||||
|
||||
@@ -2,11 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import type { GitHubWorkItem } from '../../../../shared/github/work-item-types'
|
||||
import {
|
||||
accumulateWorkItemPages,
|
||||
applyEmptyPageClamp,
|
||||
applyWindowPageLimit,
|
||||
buildSelectedReposKey,
|
||||
deriveAdvertisedTotalPages,
|
||||
resolveEmptyPageOutcome,
|
||||
workItemIdentity
|
||||
} from './task-page-work-item-pagination'
|
||||
|
||||
@@ -44,162 +40,6 @@ describe('workItemIdentity', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveEmptyPageOutcome', () => {
|
||||
const base = { failedCount: 0, errorTypes: [] as const, countedTotalPages: null }
|
||||
|
||||
it('clamps and reports unreachable when the search window 422 is present', () => {
|
||||
expect(
|
||||
resolveEmptyPageOutcome({ ...base, target: 33, errorTypes: ['validation_error'] })
|
||||
).toEqual({ reason: 'window-unreachable', clampTotalPagesTo: 33 })
|
||||
expect(
|
||||
resolveEmptyPageOutcome({
|
||||
...base,
|
||||
target: 33,
|
||||
errorTypes: ['validation_error'],
|
||||
countedTotalPages: 39
|
||||
})
|
||||
).toEqual({ reason: 'window-unreachable', clampTotalPagesTo: 33 })
|
||||
})
|
||||
|
||||
it('resolves as load-failed when a window 422 coincides with a sibling failure', () => {
|
||||
// A sibling failure says nothing about healthy repos’ remaining pages and must outrank a window clamp.
|
||||
expect(
|
||||
resolveEmptyPageOutcome({
|
||||
...base,
|
||||
target: 5,
|
||||
failedCount: 1,
|
||||
errorTypes: ['validation_error']
|
||||
})
|
||||
).toEqual({ reason: 'load-failed', clampTotalPagesTo: null })
|
||||
expect(
|
||||
resolveEmptyPageOutcome({
|
||||
...base,
|
||||
target: 5,
|
||||
errorTypes: ['validation_error', 'permission_denied']
|
||||
})
|
||||
).toEqual({ reason: 'load-failed', clampTotalPagesTo: null })
|
||||
})
|
||||
|
||||
it('reports a failure without clamping for transient or unclassified errors', () => {
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 5, failedCount: 2 })).toEqual({
|
||||
reason: 'load-failed',
|
||||
clampTotalPagesTo: null
|
||||
})
|
||||
for (const type of ['permission_denied', 'not_found', 'rate_limited', 'unknown'] as const) {
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 5, errorTypes: [type] })).toEqual({
|
||||
reason: 'load-failed',
|
||||
clampTotalPagesTo: null
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('withdraws the speculative page on clean empty only while the count is unknown', () => {
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 2 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: 2
|
||||
})
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 2, countedTotalPages: 0 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: 2
|
||||
})
|
||||
// PR failures can become clean-empty results; shrinking a real count would silently hide healthy pages.
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 2, countedTotalPages: 34 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: null
|
||||
})
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 0 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyEmptyPageClamp', () => {
|
||||
const clean = { failedCount: 0, errorTypes: [] as const }
|
||||
|
||||
it('keeps a real count that resolved between click and response', () => {
|
||||
// The count can resolve during fetch; clamp against committed state rather than the click-time closure.
|
||||
expect(applyEmptyPageClamp(33, { ...clean, target: 2 })).toBe(33)
|
||||
})
|
||||
|
||||
it('withdraws the speculative page while the count is unknown or failed', () => {
|
||||
expect(applyEmptyPageClamp(null, { ...clean, target: 2 })).toBe(2)
|
||||
expect(applyEmptyPageClamp(0, { ...clean, target: 2 })).toBe(2)
|
||||
})
|
||||
|
||||
it('never touches the count for window or failure outcomes', () => {
|
||||
expect(
|
||||
applyEmptyPageClamp(39, { target: 33, failedCount: 0, errorTypes: ['validation_error'] })
|
||||
).toBe(39)
|
||||
expect(applyEmptyPageClamp(33, { target: 5, failedCount: 2, errorTypes: [] })).toBe(33)
|
||||
expect(applyEmptyPageClamp(null, { target: 5, failedCount: 2, errorTypes: [] })).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyWindowPageLimit', () => {
|
||||
it('sets, only lowers, and never goes below one page', () => {
|
||||
expect(applyWindowPageLimit(null, 33)).toBe(33)
|
||||
expect(applyWindowPageLimit(33, 20)).toBe(20)
|
||||
expect(applyWindowPageLimit(20, 33)).toBe(20)
|
||||
expect(applyWindowPageLimit(null, 0)).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveAdvertisedTotalPages', () => {
|
||||
it('is order-independent: a count landing after a speculative withdrawal wins', () => {
|
||||
// A late real count must overwrite speculative end-of-data withdrawal.
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 1,
|
||||
countedTotalPages: 39,
|
||||
fallbackTotalPages: 2,
|
||||
provenPageLimit: null
|
||||
})
|
||||
).toBe(39)
|
||||
})
|
||||
|
||||
it('caps a real count with the proven window limit', () => {
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 3,
|
||||
countedTotalPages: 39,
|
||||
fallbackTotalPages: 4,
|
||||
provenPageLimit: 33
|
||||
})
|
||||
).toBe(33)
|
||||
})
|
||||
|
||||
it('uses the fallback while the count is unknown/failed, still window-capped', () => {
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 1,
|
||||
countedTotalPages: null,
|
||||
fallbackTotalPages: 2,
|
||||
provenPageLimit: null
|
||||
})
|
||||
).toBe(2)
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 1,
|
||||
countedTotalPages: 0,
|
||||
fallbackTotalPages: 5,
|
||||
provenPageLimit: 3
|
||||
})
|
||||
).toBe(3)
|
||||
})
|
||||
|
||||
it('never advertises fewer pages than are loaded', () => {
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 5,
|
||||
countedTotalPages: 2,
|
||||
fallbackTotalPages: 2,
|
||||
provenPageLimit: 2
|
||||
})
|
||||
).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildSelectedReposKey', () => {
|
||||
const contextFor = (r: { id: string }) => ({ provider: 'github', repoId: r.id })
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ClassifiedError } from '../../../../shared/classified-error'
|
||||
import type { GitHubWorkItem } from '../../../../shared/github/work-item-types'
|
||||
|
||||
/** Inclusive updatedAt cursors preserve timestamp ties; dedup boundary rows and backfill full display pages, flushing partials on exhaustion/stall. */
|
||||
@@ -8,70 +7,6 @@ export function workItemIdentity(item: Pick<GitHubWorkItem, 'id' | 'repoId'>): s
|
||||
return `${item.repoId}:${item.id}`
|
||||
}
|
||||
|
||||
export type EmptyPageOutcome = {
|
||||
reason: 'window-unreachable' | 'load-failed' | 'end-of-data'
|
||||
/** Count clamp or null; window limits belong in applyWindowPageLimit, never the count slot. */
|
||||
clampTotalPagesTo: number | null
|
||||
}
|
||||
|
||||
/** Only all-window 422 errors prove a cap; transient sibling failures win, and clean-empty results may withdraw speculative counts only. */
|
||||
export function resolveEmptyPageOutcome(args: {
|
||||
target: number
|
||||
failedCount: number
|
||||
errorTypes: readonly ClassifiedError['type'][]
|
||||
countedTotalPages: number | null
|
||||
}): EmptyPageOutcome {
|
||||
const clamp = Math.max(1, Math.floor(args.target))
|
||||
// A sibling non-window error may hide healthy pages, so it must block the clamp.
|
||||
const onlyWindowErrors =
|
||||
args.errorTypes.length > 0 && args.errorTypes.every((type) => type === 'validation_error')
|
||||
if (onlyWindowErrors && args.failedCount === 0) {
|
||||
return { reason: 'window-unreachable', clampTotalPagesTo: clamp }
|
||||
}
|
||||
if (args.failedCount > 0 || args.errorTypes.length > 0) {
|
||||
return { reason: 'load-failed', clampTotalPagesTo: null }
|
||||
}
|
||||
const countUnknown = args.countedTotalPages === null || args.countedTotalPages === 0
|
||||
return { reason: 'end-of-data', clampTotalPagesTo: countUnknown ? clamp : null }
|
||||
}
|
||||
|
||||
/** Clamp speculative counts against the latest committed state; store proven 422 limits separately so late real counts can replace speculation. */
|
||||
export function applyEmptyPageClamp(
|
||||
previous: number | null,
|
||||
args: {
|
||||
target: number
|
||||
failedCount: number
|
||||
errorTypes: readonly ClassifiedError['type'][]
|
||||
}
|
||||
): number | null {
|
||||
const outcome = resolveEmptyPageOutcome({ ...args, countedTotalPages: previous })
|
||||
if (outcome.reason !== 'end-of-data' || outcome.clampTotalPagesTo === null) {
|
||||
return previous
|
||||
}
|
||||
return outcome.clampTotalPagesTo
|
||||
}
|
||||
|
||||
/** Proven window 422 limit: set once, only ever lowered, reset per generation. */
|
||||
export function applyWindowPageLimit(previous: number | null, target: number): number {
|
||||
const clamp = Math.max(1, Math.floor(target))
|
||||
return previous === null ? clamp : Math.min(previous, clamp)
|
||||
}
|
||||
|
||||
/** Cap count/fallback by the proven window limit, retaining loaded pages; separate state makes late count updates order-independent. */
|
||||
export function deriveAdvertisedTotalPages(args: {
|
||||
loadedPages: number
|
||||
countedTotalPages: number | null
|
||||
fallbackTotalPages: number
|
||||
provenPageLimit: number | null
|
||||
}): number {
|
||||
const uncapped =
|
||||
args.countedTotalPages && args.countedTotalPages > 0
|
||||
? Math.max(args.loadedPages, args.countedTotalPages)
|
||||
: args.fallbackTotalPages
|
||||
const capped = args.provenPageLimit === null ? uncapped : Math.min(uncapped, args.provenPageLimit)
|
||||
return Math.max(args.loadedPages, capped)
|
||||
}
|
||||
|
||||
/** Key selection by request fields and resolved timestamp-free source context so fresh repo arrays cannot reset pagination mid-click. */
|
||||
export function buildSelectedReposKey<
|
||||
T extends {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getWorkItemPageErrorTypes } from '../../../../shared/github/work-item-page-errors'
|
||||
import type { StateCreator } from 'zustand'
|
||||
import type { AppState } from '../types'
|
||||
import type { GitHubSlice } from './slice-types'
|
||||
@@ -10,10 +11,7 @@ import {
|
||||
PER_REPO_FETCH_LIMIT
|
||||
} from '../../../../shared/work-items'
|
||||
import { getTaskSourceCacheScope } from '../../../../shared/task-source-context'
|
||||
import {
|
||||
GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN,
|
||||
isGitHubWorkItemsQueryTooLarge
|
||||
} from '../slices/github-work-items-query-bounds'
|
||||
import { isGitHubWorkItemsQueryTooLarge } from '../slices/github-work-items-query-bounds'
|
||||
import { workItemsCacheKey, workItemsInflightRequestKey } from './cache-identity'
|
||||
import { isFresh, WORK_ITEMS_CACHE_TTL } from './cache-policy'
|
||||
import {
|
||||
@@ -132,25 +130,15 @@ export const createWorkItemAggregateActions = (
|
||||
...(options?.noCache ? { noCache: true } : {}),
|
||||
...(options?.wakeWslDistro ? { wakeWslDistro: true } : {})
|
||||
})
|
||||
errorTypes.push(...getWorkItemPageErrorTypes(envelope.errors))
|
||||
// Page-N errors bypass initial-fetch banners; log them to expose silent truncation (design §6).
|
||||
if (envelope.errors?.issues) {
|
||||
const { type, message } = envelope.errors.issues
|
||||
// Only 1000-result-window 422 implies unreachable clamp; classify other validation errors as failures.
|
||||
errorTypes.push(
|
||||
type === 'validation_error' &&
|
||||
!GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN.test(message)
|
||||
? 'unknown'
|
||||
: type
|
||||
)
|
||||
console.warn(
|
||||
`[workItems] next page ${r.repoId} issues-side partial failure:`,
|
||||
envelope.errors.issues
|
||||
)
|
||||
}
|
||||
if (envelope.errors?.prs) {
|
||||
// Window 422 applies only to issues; PR validation failures never imply unreachable.
|
||||
const { type } = envelope.errors.prs
|
||||
errorTypes.push(type === 'validation_error' ? 'unknown' : type)
|
||||
console.warn(
|
||||
`[workItems] next page ${r.repoId} prs-side partial failure:`,
|
||||
envelope.errors.prs
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ClassifiedError } from '../classified-error'
|
||||
import { GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN } from './work-items-query-bounds'
|
||||
|
||||
export function getWorkItemPageErrorTypes(errors: unknown): ClassifiedError['type'][] {
|
||||
if (errors === undefined || errors === null) {
|
||||
return []
|
||||
}
|
||||
if (typeof errors !== 'object') {
|
||||
return ['unknown']
|
||||
}
|
||||
const sources = [
|
||||
['issues', 'issues' in errors ? errors.issues : undefined],
|
||||
['prs', 'prs' in errors ? errors.prs : undefined]
|
||||
] as const
|
||||
return sources.flatMap(([source, error]): ClassifiedError['type'][] => {
|
||||
if (error === undefined || error === null) {
|
||||
return []
|
||||
}
|
||||
if (typeof error !== 'object' || !('type' in error)) {
|
||||
return ['unknown']
|
||||
}
|
||||
switch (error.type) {
|
||||
case 'validation_error':
|
||||
// Only issue search's 1000-result window proves a page limit.
|
||||
return [
|
||||
source === 'issues' &&
|
||||
'message' in error &&
|
||||
typeof error.message === 'string' &&
|
||||
GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN.test(error.message)
|
||||
? 'validation_error'
|
||||
: 'unknown'
|
||||
]
|
||||
case 'permission_denied':
|
||||
case 'not_found':
|
||||
case 'issues_disabled':
|
||||
case 'rate_limited':
|
||||
case 'network_error':
|
||||
return [error.type]
|
||||
default:
|
||||
return ['unknown']
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
applyEmptyPageClamp,
|
||||
resolveEmptyPageOutcome,
|
||||
applyWindowPageLimit,
|
||||
deriveAdvertisedTotalPages
|
||||
} from './work-item-empty-page'
|
||||
|
||||
describe('resolveEmptyPageOutcome', () => {
|
||||
const base = { failedCount: 0, errorTypes: [] as const, countedTotalPages: null }
|
||||
|
||||
it('clamps and reports unreachable when the search window 422 is present', () => {
|
||||
expect(
|
||||
resolveEmptyPageOutcome({ ...base, target: 33, errorTypes: ['validation_error'] })
|
||||
).toEqual({ reason: 'window-unreachable', clampTotalPagesTo: 33 })
|
||||
expect(
|
||||
resolveEmptyPageOutcome({
|
||||
...base,
|
||||
target: 33,
|
||||
errorTypes: ['validation_error'],
|
||||
countedTotalPages: 39
|
||||
})
|
||||
).toEqual({ reason: 'window-unreachable', clampTotalPagesTo: 33 })
|
||||
})
|
||||
|
||||
it('resolves as load-failed when a window 422 coincides with a sibling failure', () => {
|
||||
// A sibling failure says nothing about healthy repos’ remaining pages and must outrank a window clamp.
|
||||
expect(
|
||||
resolveEmptyPageOutcome({
|
||||
...base,
|
||||
target: 5,
|
||||
failedCount: 1,
|
||||
errorTypes: ['validation_error']
|
||||
})
|
||||
).toEqual({ reason: 'load-failed', clampTotalPagesTo: null })
|
||||
expect(
|
||||
resolveEmptyPageOutcome({
|
||||
...base,
|
||||
target: 5,
|
||||
errorTypes: ['validation_error', 'permission_denied']
|
||||
})
|
||||
).toEqual({ reason: 'load-failed', clampTotalPagesTo: null })
|
||||
})
|
||||
|
||||
it('reports a failure without clamping for transient or unclassified errors', () => {
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 5, failedCount: 2 })).toEqual({
|
||||
reason: 'load-failed',
|
||||
clampTotalPagesTo: null
|
||||
})
|
||||
for (const type of ['permission_denied', 'not_found', 'rate_limited', 'unknown'] as const) {
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 5, errorTypes: [type] })).toEqual({
|
||||
reason: 'load-failed',
|
||||
clampTotalPagesTo: null
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('withdraws the speculative page on clean empty only while the count is unknown', () => {
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 2 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: 2
|
||||
})
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 2, countedTotalPages: 0 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: 2
|
||||
})
|
||||
// PR failures can become clean-empty results; shrinking a real count would silently hide healthy pages.
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 2, countedTotalPages: 34 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: null
|
||||
})
|
||||
expect(resolveEmptyPageOutcome({ ...base, target: 0 })).toEqual({
|
||||
reason: 'end-of-data',
|
||||
clampTotalPagesTo: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyEmptyPageClamp', () => {
|
||||
const clean = { failedCount: 0, errorTypes: [] as const }
|
||||
|
||||
it('keeps a real count that resolved between click and response', () => {
|
||||
// The count can resolve during fetch; clamp against committed state rather than the click-time closure.
|
||||
expect(applyEmptyPageClamp(33, { ...clean, target: 2 })).toBe(33)
|
||||
})
|
||||
|
||||
it('withdraws the speculative page while the count is unknown or failed', () => {
|
||||
expect(applyEmptyPageClamp(null, { ...clean, target: 2 })).toBe(2)
|
||||
expect(applyEmptyPageClamp(0, { ...clean, target: 2 })).toBe(2)
|
||||
})
|
||||
|
||||
it('never touches the count for window or failure outcomes', () => {
|
||||
expect(
|
||||
applyEmptyPageClamp(39, { target: 33, failedCount: 0, errorTypes: ['validation_error'] })
|
||||
).toBe(39)
|
||||
expect(applyEmptyPageClamp(33, { target: 5, failedCount: 2, errorTypes: [] })).toBe(33)
|
||||
expect(applyEmptyPageClamp(null, { target: 5, failedCount: 2, errorTypes: [] })).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyWindowPageLimit', () => {
|
||||
it('sets, only lowers, and never goes below one page', () => {
|
||||
expect(applyWindowPageLimit(null, 33)).toBe(33)
|
||||
expect(applyWindowPageLimit(33, 20)).toBe(20)
|
||||
expect(applyWindowPageLimit(20, 33)).toBe(20)
|
||||
expect(applyWindowPageLimit(null, 0)).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveAdvertisedTotalPages', () => {
|
||||
it('is order-independent: a count landing after a speculative withdrawal wins', () => {
|
||||
// A late real count must overwrite speculative end-of-data withdrawal.
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 1,
|
||||
countedTotalPages: 39,
|
||||
fallbackTotalPages: 2,
|
||||
provenPageLimit: null
|
||||
})
|
||||
).toBe(39)
|
||||
})
|
||||
|
||||
it('caps a real count with the proven window limit', () => {
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 3,
|
||||
countedTotalPages: 39,
|
||||
fallbackTotalPages: 4,
|
||||
provenPageLimit: 33
|
||||
})
|
||||
).toBe(33)
|
||||
})
|
||||
|
||||
it('uses the fallback while the count is unknown/failed, still window-capped', () => {
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 1,
|
||||
countedTotalPages: null,
|
||||
fallbackTotalPages: 2,
|
||||
provenPageLimit: null
|
||||
})
|
||||
).toBe(2)
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 1,
|
||||
countedTotalPages: 0,
|
||||
fallbackTotalPages: 5,
|
||||
provenPageLimit: 3
|
||||
})
|
||||
).toBe(3)
|
||||
})
|
||||
|
||||
it('never advertises fewer pages than are loaded', () => {
|
||||
expect(
|
||||
deriveAdvertisedTotalPages({
|
||||
loadedPages: 5,
|
||||
countedTotalPages: 2,
|
||||
fallbackTotalPages: 2,
|
||||
provenPageLimit: 2
|
||||
})
|
||||
).toBe(5)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { ClassifiedError } from './classified-error'
|
||||
|
||||
export type EmptyPageOutcome = {
|
||||
reason: 'window-unreachable' | 'load-failed' | 'end-of-data'
|
||||
/** Count clamp or null; window limits belong in applyWindowPageLimit, never the count slot. */
|
||||
clampTotalPagesTo: number | null
|
||||
}
|
||||
|
||||
/** Only all-window 422 errors prove a cap; transient sibling failures win, and clean-empty results may withdraw speculative counts only. */
|
||||
export function resolveEmptyPageOutcome(args: {
|
||||
target: number
|
||||
failedCount: number
|
||||
errorTypes: readonly ClassifiedError['type'][]
|
||||
countedTotalPages: number | null
|
||||
}): EmptyPageOutcome {
|
||||
const clamp = Math.max(1, Math.floor(args.target))
|
||||
// A sibling non-window error may hide healthy pages, so it must block the clamp.
|
||||
const onlyWindowErrors =
|
||||
args.errorTypes.length > 0 && args.errorTypes.every((type) => type === 'validation_error')
|
||||
if (onlyWindowErrors && args.failedCount === 0) {
|
||||
return { reason: 'window-unreachable', clampTotalPagesTo: clamp }
|
||||
}
|
||||
if (args.failedCount > 0 || args.errorTypes.length > 0) {
|
||||
return { reason: 'load-failed', clampTotalPagesTo: null }
|
||||
}
|
||||
const countUnknown = args.countedTotalPages === null || args.countedTotalPages === 0
|
||||
return { reason: 'end-of-data', clampTotalPagesTo: countUnknown ? clamp : null }
|
||||
}
|
||||
|
||||
/** Clamp speculative counts against the latest committed state; store proven 422 limits separately so late real counts can replace speculation. */
|
||||
export function applyEmptyPageClamp(
|
||||
previous: number | null,
|
||||
args: {
|
||||
target: number
|
||||
failedCount: number
|
||||
errorTypes: readonly ClassifiedError['type'][]
|
||||
}
|
||||
): number | null {
|
||||
const outcome = resolveEmptyPageOutcome({ ...args, countedTotalPages: previous })
|
||||
if (outcome.reason !== 'end-of-data' || outcome.clampTotalPagesTo === null) {
|
||||
return previous
|
||||
}
|
||||
return outcome.clampTotalPagesTo
|
||||
}
|
||||
|
||||
/** Proven window 422 limit: set once, only ever lowered, reset per generation. */
|
||||
export function applyWindowPageLimit(previous: number | null, target: number): number {
|
||||
const clamp = Math.max(1, Math.floor(target))
|
||||
return previous === null ? clamp : Math.min(previous, clamp)
|
||||
}
|
||||
|
||||
/** Cap count/fallback by the proven window limit, retaining loaded pages; separate state makes late count updates order-independent. */
|
||||
export function deriveAdvertisedTotalPages(args: {
|
||||
loadedPages: number
|
||||
countedTotalPages: number | null
|
||||
fallbackTotalPages: number
|
||||
provenPageLimit: number | null
|
||||
}): number {
|
||||
const uncapped =
|
||||
args.countedTotalPages && args.countedTotalPages > 0
|
||||
? Math.max(args.loadedPages, args.countedTotalPages)
|
||||
: args.fallbackTotalPages
|
||||
const capped = args.provenPageLimit === null ? uncapped : Math.min(uncapped, args.provenPageLimit)
|
||||
return Math.max(args.loadedPages, capped)
|
||||
}
|
||||
Reference in New Issue
Block a user