Skip to content

Commit 054d04d

Browse files
authored
Merge pull request #103 from aws-solutions-library-samples/fix/ui-doc-list-failure
Fix/UI doc list failure
2 parents 775c0b0 + 37ea2ab commit 054d04d

File tree

4 files changed

+67
-12
lines changed

4 files changed

+67
-12
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ SPDX-License-Identifier: MIT-0
55

66
## [Unreleased]
77

8+
### Fixed
9+
10+
- **UI Robustness for Orphaned List Entries** - [#102](https://github.com/aws-solutions-library-samples/accelerated-intelligent-document-processing-on-aws/issues/102)
11+
- Fixed UI error banner "failed to get document details - please try again later" appearing when orphaned list entries exist (list# items without corresponding doc# items in DynamoDB tracking table)
12+
- **Root Cause**: When a document had a list entry but no corresponding document record, the error would trigger UI banner and prevent display of all documents in the same time shard
13+
- **Solution**: Enhanced error handling to gracefully handle missing documents - now only shows error banner if ALL documents fail to load, not just one
14+
- **Enhanced Debugging**: Added detailed console logging with full PK/SK information for both list entries and expected document entries to facilitate cleanup of orphaned records
15+
- **User Impact**: All valid documents now display correctly even when orphaned list entries exist; debugging information available in browser console for identifying problematic entries
16+
817
## [0.3.21]
918

1019
### Added

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.4.0-wip1
1+
0.4.0-wip2

lib/idp_common_pkg/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ test = [
126126
"pytest-asyncio>=1.1.0",
127127
"typer>=0.19.2",
128128
"ruff>=0.14.0",
129+
"deepdiff>=6.0.0", # Required for BDA blueprint service tests
130+
"datamodel-code-generator>=0.25.0", # Required for schema/pydantic generator tests
129131
]
130132

131133
# Full package with all dependencies

src/ui/src/hooks/use-graphql-api.js

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,39 @@ const useGraphQlApi = ({ initialPeriodsToLoad = DOCUMENT_LIST_SHARDS_PER_DAY * 2
7272
client.graphql({ query: getDocument, variables: { objectKey } }),
7373
);
7474
const getDocumentResolutions = await Promise.allSettled(getDocumentPromises);
75+
76+
// Separate rejected promises from null/undefined results
7577
const getDocumentRejected = getDocumentResolutions.filter((r) => r.status === 'rejected');
76-
if (getDocumentRejected.length) {
77-
setErrorMessage('failed to get document details - please try again later');
78-
logger.error('get document promises rejected', getDocumentRejected);
78+
const fulfilledResults = getDocumentResolutions.filter((r) => r.status === 'fulfilled');
79+
const getDocumentNull = fulfilledResults
80+
.map((r, idx) => ({ doc: r.value?.data?.getDocument, key: objectKeys[idx] }))
81+
.filter((item) => !item.doc)
82+
.map((item) => item.key);
83+
84+
// Log partial failures but NEVER show error banner for individual document failures
85+
if (getDocumentRejected.length > 0) {
86+
logger.warn(
87+
`Failed to load ${getDocumentRejected.length} of ${objectKeys.length} document(s) due to query rejection`,
88+
);
89+
logger.debug('Rejected promises:', getDocumentRejected);
7990
}
91+
if (getDocumentNull.length > 0) {
92+
logger.warn(
93+
`${getDocumentNull.length} of ${objectKeys.length} document(s) not found (returned null):`,
94+
getDocumentNull,
95+
);
96+
logger.warn(
97+
'These documents have list entries but no corresponding document records - possible orphaned list entries',
98+
);
99+
}
100+
101+
// Filter out null/undefined documents to prevent downstream errors
80102
const documentValues = getDocumentResolutions
81103
.filter((r) => r.status === 'fulfilled')
82-
.map((r) => r.value?.data?.getDocument);
104+
.map((r) => r.value?.data?.getDocument)
105+
.filter((doc) => doc != null);
83106

107+
logger.debug(`Successfully loaded ${documentValues.length} of ${objectKeys.length} requested documents`);
84108
return documentValues;
85109
},
86110
[setErrorMessage],
@@ -256,11 +280,26 @@ const useGraphQlApi = ({ initialPeriodsToLoad = DOCUMENT_LIST_SHARDS_PER_DAY * 2
256280
const documentData = await documentDataPromise;
257281
const objectKeys = documentData.map((item) => item.ObjectKey);
258282
const documentDetails = await getDocumentDetailsFromIds(objectKeys);
259-
// Merge document details with PK and SK
260-
return documentDetails.map((detail) => {
261-
const matchingData = documentData.find((item) => item.ObjectKey === detail.ObjectKey);
262-
return { ...detail, ListPK: matchingData.PK, ListSK: matchingData.SK };
263-
});
283+
284+
// Log orphaned list entries with full PK/SK details for debugging
285+
const retrievedKeys = new Set(documentDetails.map((d) => d.ObjectKey));
286+
const missingDocs = documentData.filter((item) => !retrievedKeys.has(item.ObjectKey));
287+
if (missingDocs.length > 0) {
288+
missingDocs.forEach((item) => {
289+
logger.warn(`Orphaned list entry detected:`);
290+
logger.warn(` - List entry: PK="${item.PK}", SK="${item.SK}"`);
291+
logger.warn(` - Expected doc entry: PK="doc#${item.ObjectKey}", SK="none"`);
292+
logger.warn(` - ObjectKey: "${item.ObjectKey}"`);
293+
});
294+
}
295+
296+
// Merge document details with PK and SK, filtering out nulls to prevent shard-level failures
297+
return documentDetails
298+
.filter((detail) => detail != null)
299+
.map((detail) => {
300+
const matchingData = documentData.find((item) => item.ObjectKey === detail.ObjectKey);
301+
return { ...detail, ListPK: matchingData.PK, ListSK: matchingData.SK };
302+
});
264303
});
265304

266305
const documentValuesPromises = documentDetailsPromises.map(async (documentValuesPromise) => {
@@ -279,9 +318,14 @@ const useGraphQlApi = ({ initialPeriodsToLoad = DOCUMENT_LIST_SHARDS_PER_DAY * 2
279318
setDocumentsDeduped(documentValuesReduced);
280319
setIsDocumentsListLoading(false);
281320
const getDocumentsRejected = getDocumentsPromiseResolutions.filter((r) => r.status === 'rejected');
282-
if (getDocumentsRejected.length) {
321+
// Only show error banner if ALL shard queries failed
322+
if (getDocumentsRejected.length === documentDataPromises.length) {
283323
setErrorMessage('failed to get document details - please try again later');
284-
logger.error('get document promises rejected', getDocumentsRejected);
324+
logger.error('All shard queries rejected', getDocumentsRejected);
325+
} else if (getDocumentsRejected.length > 0) {
326+
// Partial failure - log but don't show error banner
327+
logger.warn(`${getDocumentsRejected.length} of ${documentDataPromises.length} shard queries failed`);
328+
logger.debug('Rejected shard queries:', getDocumentsRejected);
285329
}
286330
} catch (error) {
287331
setIsDocumentsListLoading(false);

0 commit comments

Comments
 (0)