Skip to content

📝 [SC-8017] Docs: Document useWidgetEvents (onInfo/onData and widget-to-widget events) - #28

Open
AgustinPelaez wants to merge 1 commit into
mainfrom
docs/sc-8017-use-widget-events
Open

AgustinPelaez wants to merge 1 commit into
mainfrom
docs/sc-8017-use-widget-events

Conversation

@AgustinPelaez

@AgustinPelaez AgustinPelaez commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Pull Request

📝 Description

Adds docs/hooks/useWidgetEvents.md and links it from docs/README.md. useWidgetEvents is the hook a Custom Metric widget gets its own data from, and until now it existed only in src/hooks/useWidgetEvents.ts with no docs page.

The page covers:

  • Signature and return fields: onInfo, onData, onReady, onError, emitWidgetEvent, onWidgetEvent, onAnyWidgetEvent and widgetId.
  • Payload shapes:
    • onInfo: templateId, variables[], devices[].
    • onData: start/end as ISO strings, and results[] with variableId, deviceId, value and an epoch-ms timestamp.
    • How to join them: results[].variableId === variables[].id.
  • Where the widget id comes from: the hook argument, or else UbidotsProvider's widgetId. Without one, the on* callbacks never fire and emit only warns.
  • Widget-to-widget events: how they're named (v2:widget:<event>:<widgetId>) and the :* prefix wildcard.
  • Two examples: rendering the configured variables, and emitting and listening between widgets.

Why now: the Ubidots editor assistants are moving from a private RAG to our public docs (web search), and this hook was the main gap (sc-8017, sc-8005). The Dev Center has the matching section: https://dev.ubidots.com/dashboards-and-widgets/custom-metric

🔗 Related Issues

  • Shortcut sc-8017: publish the Custom Metric widget docs.

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix
  • 🏗️ Build/CI changes
  • 📦 Dependency updates

🧪 Testing

Docs only: no source, test or build changes, so I didn't run pnpm test, lint, typecheck or build. The documented behaviour was checked against src/hooks/useWidgetEvents.ts on main.

  • Unit tests pass (pnpm test)
  • Linting passes (pnpm lint)
  • Type checking passes (pnpm typecheck)
  • Build succeeds (pnpm build)
  • Manual testing completed (behaviour checked against the source)

Test Instructions

  1. Open docs/hooks/useWidgetEvents.md and check it renders, and that its links (UbidotsProvider, the related hooks, the complete-widget example) resolve.
  2. Compare the event naming with emitWidgetEvent (`v2:widget:${event}:${widgetId}`) and the wildcard branch (key.endsWith(':*')) in src/hooks/useWidgetEvents.ts.

✅ Checklist

Code Quality

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Documentation

  • I have made corresponding changes to the documentation
  • I have updated the README if necessary
  • I have added/updated JSDoc comments for new functions/components

Dependencies

  • I have checked that no unnecessary dependencies were added
  • All new dependencies are properly justified in the PR description
  • I have updated the package.json version if this is a release PR

Breaking Changes

  • I have documented any breaking changes
  • I have updated migration guides if necessary
  • I have considered backward compatibility

🚀 Deployment Notes

None. Docs only, no version bump.

📋 Additional Notes

  • The hook's JSDoc disagrees with its code. It says events follow v2:widget:<widgetId>:<event>, but emitWidgetEvent sends v2:widget:<event>:<widgetId>. This page documents what the code does. The JSDoc should be fixed in a follow-up.
  • onWidgetEvent matches the full event name. onWidgetEvent('highlight', cb) never fires: subscribe to 'v2:widget:highlight:*' or to the sender's full name. The Best Practices section says so, because the earlier internal docs showed the short form.

Reviewer Guidelines:

  • Ensure all CI checks pass
  • Verify code follows project conventions
  • Check for proper error handling
  • Validate test coverage
  • Review documentation updates

🤖 Generated with Claude Code

…to-widget events)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔍 PR Quality Check Summary

Check Status
Code Quality & Tests ❌ failure
Security Audit ❌ failure

📋 Checks Performed:

  • ✅ TypeScript compilation
  • ✅ ESLint code quality
  • ✅ Prettier code formatting
  • ✅ Unit tests with coverage (80%+ required)
  • ✅ Build verification
  • ✅ Security audit

⚠️ Some checks failed. Please review the details above.

@AgustinPelaez AgustinPelaez changed the title 📝 [SC-8017] Docs: Document useWidgetEvents (onInfo/onData and widget-… 📝 [SC-8017] Docs: Document useWidgetEvents (onInfo/onData and widget-to-widget events) Oct 20, 2026

@rarc41 rarc41 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisé el doc contra lo que hace el dashboard hoy (el CustomMetricWidget de ubi.frontend y los templates de ubidots-widgets). Las formas de onInfo y onData coinciden con buildWidgetInfo y normalizeWidgetData, y también están bien el nombre v2:widget:<event>:<widgetId>, el comodín :* y lo del JSDoc invertido.

Dejo en línea tres puntos donde el doc describe algo que el dashboard no hace (eventos entre widgets, formato de start/end, onReady/onError) y algunos menores. Lo verifiqué leyendo el código, no en runtime.

Las páginas Widget data y Widget events del Dev Center tienen los mismos tres puntos, y Widget events todavía usa onWidgetEvent('highlight', …), la forma corta que nunca se dispara.


### Widget-to-widget events

The sender's id is part of the event name. To hear an event from any widget, subscribe with a wildcard:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El dashboard no reenvía estos eventos a los otros widgets. emitWidgetEvent hace window.parent.postMessage, así que el mensaje llega solo al dashboard. En el frontend, cada WidgetEventHandler le escribe únicamente a su propio iframe, y los listeners de message del dashboard solo atienden nombres puntuales (setFullScreen, el rango de fechas, v2:widget:ready:<id>). Nada toma un v2:widget:highlight:<id> y lo manda a los demás iframes, así que este ejemplo no hace nada en un dashboard real y tampoco muestra error.

El asistente del editor tiene una entrada para widget-to-widget en su índice de capacidades, así que va a encontrar esta sección y generar código con ella. Propongo marcarla como todavía no soportada por el dashboard (o sacarla) hasta que el frontend reenvíe estos eventos. Para confirmarlo basta con dos Custom Metric en un mismo dashboard.


```js
{
start, // ISO 8601 string or undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En el dashboard, start y end llegan en epoch ms, no en ISO. CustomMetricWidget pasa initialDate/finalDate sin convertirlos, y esas props son PropTypes.number en toda la cadena (Dashboard.jsx, DashboardGrid/Widget.jsx). Quien manda ISO es el preview del editor (previewEmitter.js, con toISOString()), así que esto describe el preview. Sugiero documentar epoch ms; el preview habría que alinearlo aparte. Mientras tanto, new Date(start) funciona con los dos formatos.

Comment on lines +34 to +35
| `onReady(cb)` | `v2:widget:ready:<widgetId>`. |
| `onError(cb)` | `v2:widget:error:<widgetId>`. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hoy el dashboard no emite ninguno de los dos, así que estos callbacks nunca se disparan dentro de un dashboard. En el frontend, emitWidgetReady y emitWidgetError no tienen quién los llame, y el ready de la línea 26 va a window.parent (el dashboard lo usa como handshake): no vuelve al iframe. Convendría decirlo aquí y aclarar en la línea 26 a dónde va ese evento.

),
[onData]
);
useEffect(() => onError(() => {/* show a friendly message */}), [onError]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Por lo mismo, este onError nunca corre. Lo sacaría del ejemplo para que nadie (tampoco el asistente) cuente con él para mostrar el estado de error.

| Field | Description |
| --- | --- |
| `onInfo(cb)` | The widget's descriptors (`v2:widget:info:<widgetId>`). Fires on load and whenever the widget's settings or devices change. |
| `onData(cb)` | The widget's values (`v2:widget:data:<widgetId>`). Fires on load and on every real-time tick. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En los ticks de tiempo real, results trae solo la variable que cambió (y en polling, solo las variables que no son last_value). El ejemplo ya acumula con { ...prev }, que es lo correcto; vale la pena decirlo también en el texto: acumula por variableId, no reemplaces.

{
start, // ISO 8601 string or undefined
end, // ISO 8601 string or undefined
results: [{ variableId, deviceId, value, timestamp }] // timestamp: epoch ms; value: number or null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timestamp también puede venir en null, no solo value: cuando la variable no tiene datos en la ventana, normalizeWidgetData deja los dos en null.


| Field | Description |
| --- | --- |
| `onInfo(cb)` | The widget's descriptors (`v2:widget:info:<widgetId>`). Fires on load and whenever the widget's settings or devices change. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

También se vuelve a disparar cuando se recargan los datos del widget (por ejemplo, al cambiar el rango de fechas), porque el payload se arma con esa respuesta. Conviene tratarlo como idempotente.


## Description

Widget events are namespaced per widget: `v2:widget:<event>:<widgetId>`. The widget id comes from the `widgetId` argument, or else from the `widgetId` prop of [`UbidotsProvider`](../UbidotsProvider.md). Without a widget id, `onInfo` / `onData` / `onReady` / `onError` never fire, and `emitWidgetEvent` only logs a warning.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En un Custom Metric, el valor está en el ?id= de la URL del iframe (en el preview también, vía un shim). Una línea con new URLSearchParams(location.search).get('id'), o un link a Project setup del Dev Center, ayudaría a evitar el error más común: sin ese id no llega nada.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants