Repository navigation
[iFrame] Add the url order tracker example to the iframe catalog - #3009
josemontespg wants to merge 1 commit into
Conversation
7a9ab37 to
4fa70f1
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces a new catalog example, url-order-tracker.json, which demonstrates a WebAppFrameUrl order tracker. It includes the corresponding fixture application order_tracker.html and comprehensive end-to-end tests to validate its functionality. The feedback suggests two robustness improvements in the fixture application: first, validating the subpath index in applyOrdersUpdate to avoid state corruption from invalid index parsing, and second, adding defensive checks for null or undefined elements in the orders array to prevent runtime errors.
| // The host diffs the array and sends each changed order as a subpath update such as "/1". | ||
| var index = Number(msg.subpath.split('/')[1]); | ||
| if (msg.value === undefined) { | ||
| orders.splice(index, 1); | ||
| } else { | ||
| orders[index] = msg.value; | ||
| } |
There was a problem hiding this comment.
In applyOrdersUpdate, parsing the index using Number(msg.subpath.split('/')[1]) can lead to unexpected behavior if the subpath is malformed or empty (e.g., Number("") evaluates to 0, and Number(undefined) evaluates to NaN). In JavaScript, calling splice(NaN, 1) will default to removing the first element of the array (index 0), which can corrupt the state.
We should defensively validate that the extracted index is a valid non-negative integer string before parsing and using it.
| // The host diffs the array and sends each changed order as a subpath update such as "/1". | |
| var index = Number(msg.subpath.split('/')[1]); | |
| if (msg.value === undefined) { | |
| orders.splice(index, 1); | |
| } else { | |
| orders[index] = msg.value; | |
| } | |
| // The host diffs the array and sends each changed order as a subpath update such as "/1". | |
| var parts = msg.subpath.split('/'); | |
| var indexStr = parts[1]; | |
| if (!/^\d+$/.test(indexStr)) { | |
| return; | |
| } | |
| var index = Number(indexStr); | |
| if (msg.value === undefined) { | |
| orders.splice(index, 1); | |
| } else { | |
| orders[index] = msg.value; | |
| } |
| function summarize() { | ||
| return STATUSES.map(function (status) { | ||
| var count = orders.filter(function (order) { | ||
| return order.status === status; | ||
| }).length; | ||
| return count ? count + ' ' + status : ''; | ||
| }) | ||
| .filter(Boolean) | ||
| .join(', '); | ||
| } | ||
|
|
||
| function render() { | ||
| var list = document.getElementById('orders'); | ||
| list.textContent = ''; | ||
| orders.forEach(function (order) { | ||
| var button = document.createElement('button'); | ||
| button.textContent = order.id + ' - ' + order.status; | ||
| button.addEventListener('click', function () { | ||
| send({type: 'a2ui_action', action: 'order_selected', data: {orderId: order.id}}); | ||
| }); | ||
| var item = document.createElement('li'); | ||
| item.appendChild(button); | ||
| list.appendChild(item); | ||
| }); | ||
| } | ||
|
|
||
| function ordersChanged() { | ||
| orders.forEach(function (order) { | ||
| var lastStatus = lastStatusById[order.id]; | ||
| if (order.status === 'delivered' && lastStatus && lastStatus !== 'delivered') { | ||
| send({type: 'a2ui_action', action: 'order_delivered', data: {orderId: order.id}}); | ||
| } | ||
| }); | ||
| lastStatusById = {}; | ||
| orders.forEach(function (order) { | ||
| lastStatusById[order.id] = order.status; | ||
| }); | ||
| render(); | ||
| // "summary" is listed in mutableData, so the host writes it back to /summary. | ||
| send({type: 'a2ui_data_model_change', key: 'summary', value: summarize()}); | ||
| send({type: 'a2ui_size_changed', height: document.body.scrollHeight + 32}); | ||
| } |
There was a problem hiding this comment.
To ensure robustness and adhere to defensive programming principles, we should guard against null or undefined elements in the orders array. If an element in orders is null or undefined, accessing properties like order.status or order.id will throw a runtime TypeError and crash the application loop inside the iframe.
Let's add nullish/falsy checks for order in summarize, render, and ordersChanged.
function summarize() {
return STATUSES.map(function (status) {
var count = orders.filter(function (order) {
return order && order.status === status;
}).length;
return count ? count + ' ' + status : '';
})
.filter(Boolean)
.join(', ');
}
function render() {
var list = document.getElementById('orders');
list.textContent = '';
orders.forEach(function (order) {
if (!order) return;
var button = document.createElement('button');
button.textContent = order.id + ' - ' + order.status;
button.addEventListener('click', function () {
send({type: 'a2ui_action', action: 'order_selected', data: {orderId: order.id}});
});
var item = document.createElement('li');
item.appendChild(button);
list.appendChild(item);
});
}
function ordersChanged() {
orders.forEach(function (order) {
if (!order) return;
var lastStatus = lastStatusById[order.id];
if (order.status === 'delivered' && lastStatus && lastStatus !== 'delivered') {
send({type: 'a2ui_action', action: 'order_delivered', data: {orderId: order.id}});
}
});
lastStatusById = {};
orders.forEach(function (order) {
if (order) {
lastStatusById[order.id] = order.status;
}
});
render();
// "summary" is listed in mutableData, so the host writes it back to /summary.
send({type: 'a2ui_data_model_change', key: 'summary', value: summarize()});
send({type: 'a2ui_size_changed', height: document.body.scrollHeight + 32});
}2864ae4 to
2e7c6f0
Compare
2e7c6f0 to
a3aa249
Compare
a3aa249 to
ce7bcd8
Compare
ce7bcd8 to
8d2e66d
Compare
8d2e66d to
2196c96
Compare
2196c96 to
ad5b3fc
Compare
ad5b3fc to
c6f34cf
Compare
c6f34cf to
41e5825
Compare
b28b184 to
2a008d5
Compare
2a008d5 to
34db087
Compare
34db087 to
d749d56
Compare
d749d56 to
1ccb19f
Compare
b3f412d to
39409f6
Compare
39409f6 to
5e19185
Compare
5e19185 to
c094f05
Compare
3b88e48 to
3d5eaf4
Compare
3d5eaf4 to
a9cb0bc
Compare
A WebAppFrameUrl app that reads the orders from bound data, writes a status summary back through mutableData and reports selected and delivered orders as actions. The example points at a placeholder origin; tests/fixtures/apps/order_tracker.html is the app behind it and tests/examples/url_order_tracker.test.ts runs the example through the real sandbox proxy with that fixture, driving it through the host data model.
Summary
Adds
url-order-tracker.json(46_url-order-tracker) to the iframe catalog examples and wires it into the Lit, Angular, and React explorer apps: aWebAppFrameUrlapp that lists the orders bound from/orders, writes a status summary back to/summaryfor a siblingText, dispatchesorder_deliveredwhen an order status turns todelivered, and dispatchesorder_selectedwhen one is clicked.Changes
catalogs/iframe/examples/url-order-tracker.json&typescript/catalogs/iframe/tests/fixtures/apps/order_tracker.html: the example and the external app HTML fixture.typescript/catalogs/iframe/tests/examples/url_order_tracker.test.ts,catalog.test.ts,README.md,CHANGELOG.md: iframe catalog example registration and Karma test.renderers/{lit,angular,react}/a2ui_explorer: serves/a2ui-fixtures/apps/order_tracker.html, rewrites placeholder URLs in46_url-order-trackerto the local origin, and adds Karma integration tests (46_url-order-tracker.{spec.ts,test.tsx}).Verification
yarn --cwd typescript/catalogs/iframe test: 244 Karma specs pass.yarn --cwd renderers/{lit,react}/a2ui_explorer test:karma: pass.python3 specification/scripts/validate.pyand./scripts/fix_format.sh --checkpass.Related to #3157