Ir al contenido

API de widgets de dashboards

Un dashboard contiene una cuadrícula de widgets. Cada widget tiene un ULID asignado por el servidor, un type de un catálogo cerrado, los campos de configuración de ese tipo, un width, un height (en todos los tipos salvo el título de sección), una position en la cuadrícula y, en todos los tipos salvo el título de sección, un alcance de proyectos.

Todo lo que describe esta página sigue las reglas del dashboard: el control por plan (403 forbidden sin un plan que incluya dashboards), 404 not_found para un dashboard que no puedes ver y 403 forbidden para un cambio de alguien que no creó el dashboard ni es propietario o administrador de la organización. Cualquiera que pueda ver un dashboard puede listar sus widgets.

typeCampos de configuraciónwidth por defectoheight por defecto
section_titleNinguno. title es obligatorio: el widget es un encabezado que divide la cuadrículafullNinguno
countmetric: test_cases, open_defects, active_runs, suites, flaky_casesthird2
created_countmetric: test_cases, defects, results, runs; rangeDaysthird2
distributioncustomFieldUlid: un campo personalizado de casos de prueba; chart: donut (por defecto) o barhalf4
time_seriesmetric: open_defects, result_events, case_events, run_pass_rate, run_average_duration; rangeDays; chart: auto (por defecto) o heatmap, solo para result_events y case_events, como máximo 365 díastwo_thirds4
pass_raterangeDays; chart: gauge (por defecto) o bullet; target: un entero de 1 a 100 (por defecto 80), contra el que se mide la barrathird3
querysavedQueryUlid: la consulta guardada que dibuja, leída en vivo; render: table, bar, line, donut o value (por defecto null, la visualización de la propia consulta); sin filtershalf4
  • rangeDays es 7, 14, 30, 90, 180, 365 o 730 (por defecto 30), contados hacia atrás desde el día en que se ve el widget. Cada widget guarda su propio rango, y todos los que ven el dashboard ven el mismo. Un heatmap admite 365 días como máximo.
  • Un distribution agrupa los casos de prueba por un campo personalizado de tipo select_single, radio, checkbox, select_multi o user_picker, de sistema o creado por la organización. Un campo que no existe devuelve 404 not_found; un campo de otro tipo, o un campo de defectos o de ejecuciones, devuelve 422 validation_failed.
  • width es third, half, two_thirds o full.
  • height es la cantidad de filas de la cuadrícula que ocupa el widget, un entero de 2 a 8. Un section_title no tiene altura y la rechaza.
  • title admite hasta 200 caracteres. En todos los tipos salvo section_title es opcional: null muestra el título por defecto del tipo.

projects define qué proyectos cuenta un widget:

  • { "mode": "all" } (por defecto): todos los proyectos que puede abrir quien ve el widget.
  • { "mode": "selected", "projectUlids": [...] }: de 1 a 50 proyectos indicados, sin repetir. Al modificar un widget, la lista puede estar vacía (ver más abajo).

Lo que cuenta un widget depende siempre de quién lo ve. Un miembro que no puede abrir un proyecto seleccionado (un proyecto privado al que no tiene acceso, un proyecto bloqueado o uno archivado) no lo ve en projects.projectUlids; projects.hiddenProjectCount le indica cuántos proyectos seleccionados quedan ocultos para él. Sus ULID nunca se devuelven.

Al agregar o modificar un widget, cada proyecto que indiques debe ser uno que puedas abrir; de lo contrario, 404 not_found indica el ULID y no se guarda nada. Modificar un alcance selected reemplaza solo los proyectos que puedes abrir: los proyectos seleccionados que no puedes ver se mantienen como están. Por eso puedes enviar projectUlids vacío para quitar todos los proyectos que puedes abrir mientras queden proyectos ocultos; un alcance selected que se quedaría sin ningún proyecto devuelve 422 validation_failed. Cambiar el alcance a all quita todos los proyectos seleccionados, incluidos los ocultos. Cuando se elimina un proyecto, sale de todos los widgets que lo seleccionaban.

Todos los tipos salvo section_title pueden tener filters, que acotan su número para todos los que ven el dashboard. Solo quienes pueden editar el dashboard los modifican.

"filters": [
{ "field": "assignee", "values": ["01J9Z3Q4M8R2T6V1W5X7Y9A0C2", "01J9Z3Q4M8R2T6V1W5X7Y9A0C3"] },
{ "field": "custom_field", "customFieldUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BF", "values": ["01J9Z3Q4M8R2T6V1W5X7Y9A0C0"] }
]
  • Como máximo 10 filtros, uno por field (uno por campo personalizado), cada uno con 1 a 50 valores, sin repetir.
  • Los valores de un filtro se combinan con O; los distintos filtros, con Y. El ejemplo cuenta los defectos asignados a cualquiera de las dos personas y que tienen esa opción.

Los campos que ofrece un widget dependen de lo que cuenta su métrica:

CuentaWidgetsfield
Casos de pruebacount test_cases y flaky_cases, created_count test_cases, time_series case_events, distributionauthor, suite, tag, milestone, custom_field
Defectoscount y time_series open_defects, created_count defectsreporter, assignee, milestone, tag, custom_field; status solo en created_count
Ejecucionescount active_runs, created_count runs, time_series run_pass_rate y run_average_duration, pass_rateauthor, environment, milestone, test_plan, tag, custom_field
Resultadoscreated_count results, time_series result_eventsstatus, executed_by, environment, milestone

Un widget count suites no acepta filtros. El entorno y el hito de un resultado son los de su ejecución. Un filtro suite también incluye los casos de las suites que están debajo.

Qué es cada valor:

  • author, reporter, assignee, executed_by: ULID de miembros.
  • suite, tag, milestone, environment, test_plan: sus ULID. Las suites, los hitos, los entornos y los planes de prueba deben estar en proyectos que puedas abrir.
  • status: open, in_progress, resolved o closed para defectos; passed, failed, skipped o blocked para resultados.
  • custom_field: customFieldUlid indica un campo de la entidad contada (caso de prueba, defecto o ejecución) de tipo select_single, radio, checkbox, select_multi o user_picker. Los valores son ULID de opciones, ULID de miembros para user_picker, o "true" / "false" para checkbox. "false" coincide solo con los elementos que guardan un valor sin marcar.

Un filtro que la métrica no ofrece, o un valor que no indica nada que puedas usar (un miembro, etiqueta, campo u opción que no existe, o una suite, hito, entorno o plan de prueba de un proyecto que no puedes abrir), devuelve 422 validation_failed; details indica la ruta de cada valor rechazado y no se guarda nada. Si más adelante se elimina una entidad filtrada, su valor se mantiene en el widget, con etiqueta null, y no coincide con nada hasta que alguien que edita el dashboard lo quite.

Los filtros se guardan, y se devuelven, en un orden fijo y con sus valores ordenados, así que enviar los mismos filtros en otro orden no cambia nada.

GET/api/v1/dashboards/{dashboardUlid}/widgets

200 con { "items": [...] }, todos los widgets por position. Un dashboard tiene como máximo 50 widgets, por lo que la lista no se pagina.

POST/api/v1/dashboards/{dashboardUlid}/widgets

Agrega un widget después del último. El cuerpo lleva type, los campos de configuración del tipo y, de forma opcional, title, width, height, projects y filters (los tres últimos no van en un section_title). El cuerpo es estricto: un campo que el tipo no tiene devuelve 422 validation_failed. Un dashboard que ya tiene 50 widgets devuelve 409 conflict.

201 con el widget. Consulta Campos de la respuesta.

Ventana de terminal
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"time_series","metric":"run_pass_rate","rangeDays":90,"projects":{"mode":"selected","projectUlids":["01J9Z3Q4M8R2T6V1W5X7Y9A0BC"]}}' \
"https://app.probara.net/api/v1/dashboards/$DASHBOARD_ULID/widgets"
PATCH/api/v1/dashboards/{dashboardUlid}/widgets/{widgetUlid}

Envía al menos uno de title, width, height, projects, metric, rangeDays, customFieldUlid, chart, target o filters. El type de un widget no cambia: quítalo y agrega otro. Un campo que el tipo del widget no tiene, o un valor que el tipo no acepta (por ejemplo, una métrica de count en un time_series), devuelve 422 validation_failed. Un título de sección conserva su título (null se rechaza) y no acepta height ni projects. Un cuerpo que no cambia nada devuelve el widget sin modificar.

filters reemplaza la lista completa; [] quita todos los filtros. Los valores que el widget ya guarda se mantienen aunque su entidad se haya eliminado; los valores nuevos se validan. Cambiar metric mientras el widget tiene un filtro que la nueva métrica no ofrece devuelve 422 validation_failed, salvo que el mismo cuerpo envíe filters.

200 con el widget.

PUT/api/v1/dashboards/{dashboardUlid}/widgets/order

Envía { "widgetUlids": [...] } con cada widget del dashboard exactamente una vez, en el nuevo orden; cualquier otra lista devuelve 422 validation_failed. 200 con los widgets en su nuevo orden.

DELETE/api/v1/dashboards/{dashboardUlid}/widgets/{widgetUlid}

204. Los widgets que estaban después suben una posición. Eliminar un dashboard quita sus widgets.

Un widget query dibuja una consulta guardada en vivo, así que una edición de la consulta llega al widget. Crea uno con "type":"query", savedQueryUlid (una consulta guardada que puedas ver) y, opcionalmente, render para reemplazar la visualización de la propia consulta (null, por defecto, la conserva). Recibe width, height y projects como cualquier widget de datos, y no recibe filters: el texto de la consulta decide sus filas. Una modificación puede apuntarlo a otra consulta (savedQueryUlid) o cambiar su render. Un widget cuya consulta guardada se eliminó conserva su lugar con savedQueryUlid en null; un editor lo quita.

Sus datos son la consulta guardada ejecutada con tu propio acceso a los proyectos y limitada a los proyectos del widget: savedQueryUlid, savedQueryName, queryDeleted, canOpenQuery (si puedes abrir la consulta, que es cuando aplica un enlace a ella), render (el dibujo en uso), scope (los mismos conteos que reporta cualquier widget de datos) y result, la primera página de la respuesta de la consulta en la forma que responde el endpoint de consultas: filas, o grupos cuando la consulta agrega. Quien puede ver el dashboard ve esa página aunque la consulta sea privada para otra persona, sin su texto. Una consulta eliminada responde queryDeleted: true con savedQueryUlid, savedQueryName, render y result en null.

Los registros de un widget query no se listan aquí: sus filas se leen en su consulta guardada, así que el endpoint de registros responde 422 validation_failed.

GET/api/v1/dashboards/{dashboardUlid}/widgets/{widgetUlid}/data

Devuelve lo que muestra un widget. Cualquiera que pueda ver el dashboard puede leerlo, y los números siempre se calculan para ti: solo cubren los proyectos del alcance del widget que puedes abrir. Los propietarios y administradores de la organización abren todos los proyectos que no están bloqueados. Los proyectos bloqueados y archivados nunca se cuentan.

{
"type": "created_count",
"widgetUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BD",
"metric": "defects",
"scope": { "includedProjectCount": 3, "hiddenProjectCount": 1, "emptySelection": false },
"range": {
"requestedDays": 365,
"effectiveDays": 90,
"from": 1751976000000,
"to": 1759752000000,
"clampedByRetention": true
},
"data": { "value": 42 }
}

scope indica si el número está completo:

  • includedProjectCount: cuántos proyectos se contaron.
  • hiddenProjectCount: cuántos quedaron fuera. En un widget selected, los proyectos seleccionados que no puedes abrir (el mismo número que projects.hiddenProjectCount en el widget). En un widget all, los proyectos de la organización que no están archivados y que no puedes abrir, incluidos los bloqueados. Si es mayor que 0, el valor es parcial: muéstralo como tal, no como el total.
  • emptySelection: true en un widget selected cuyos proyectos se eliminaron todos. Su valor es 0.

Qué cuenta cada métrica:

typemetricdata.value
counttest_casesCasos de prueba no archivados
countopen_defectsDefectos con estado open o in_progress
countactive_runsEjecuciones que no están cerradas
countsuitesSuites
countflaky_casesCasos de prueba no archivados con Is flaky marcado
created_counttest_casesCasos de prueba creados en el rango, incluidos los que se archivaron después
created_countdefectsDefectos creados en el rango
created_countresultsResultados registrados en el rango, según su hora de ejecución
created_countrunsEjecuciones iniciadas en el rango

Un widget created_count también devuelve range, la ventana que contó: desde from (incluido) hasta to (excluido), en milisegundos desde epoch, terminando en el momento de la solicitud. La ventana es el rangeDays del widget, recortado a la retención del historial de tu plan: las ejecuciones cerradas más antiguas se eliminan, así que una ventana más larga contaría de menos. effectiveDays es la duración que realmente se contó, y clampedByRetention es true cuando la retención la recortó.

Un widget distribution cuenta los casos de prueba del alcance que no están archivados y pertenecen a un proyecto donde se aplica su campo, agrupados por el valor que tienen en ese campo.

{
"type": "distribution",
"widgetUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BE",
"customFieldUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BF",
"scope": { "includedProjectCount": 2, "hiddenProjectCount": 0, "emptySelection": false },
"data": {
"fieldRemoved": false,
"field": {
"ulid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BF",
"title": "Prioridad",
"type": "select_single"
},
"multiValue": false,
"totalCases": 120,
"buckets": [
{
"kind": "option",
"key": "01J9Z3Q4M8R2T6V1W5X7Y9A0C0",
"label": "Alta",
"color": "#a890e8",
"count": 30
},
{
"kind": "option",
"key": "01J9Z3Q4M8R2T6V1W5X7Y9A0C1",
"label": "Baja",
"color": "#a89fb2",
"count": 75
},
{ "kind": "not_set", "key": null, "count": 15 }
]
}
}

Cada grupo tiene un kind:

kindkeyNotas
optionULID de la opciónCampos de selección y de opción única. Se listan todas las opciones en el orden del campo, incluidas las vacías. label es el nombre en el idioma de la solicitud
checkboxtrue/falseMarcado y sin marcar
userULID del usuarioCampos de usuario, de mayor a menor. formerMember es true cuando la persona dejó la organización; label es null cuando la organización ya no la conoce
otherotherA partir de 20 grupos, los más pequeños se juntan en este; groupedCount indica cuántos
not_setnullSiempre al final: casos sin valor, o cuyo valor apunta a una opción eliminada

totalCases es la cantidad de casos contados. En un campo de selección múltiple, multiValue es true: un caso cuenta una vez en cada opción que tiene, así que la suma de los grupos puede superar totalCases.

Si el campo se eliminó después de agregar el widget, la respuesta sigue siendo 200, con data igual a { "fieldRemoved": true, "buckets": [] }.

Un widget pass_rate devuelve range y un único objeto data. value es la tasa de aprobación agregada de las ejecuciones cerradas en el rango, en porcentaje con un decimal: passed sobre executed, es decir, los casos aprobados, fallidos, bloqueados u omitidos. Se excluyen las ejecuciones abortadas, y value es null cuando no se ejecutó nada. closedRuns cuenta las ejecuciones que agrupa, y sus registros listan esas mismas ejecuciones:

{
"type": "pass_rate",
"widgetUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BC",
"scope": { "includedProjectCount": 2, "hiddenProjectCount": 0, "emptySelection": false },
"range": {
"requestedDays": 30,
"effectiveDays": 30,
"from": 1756742400000,
"to": 1759334400000,
"clampedByRetention": false
},
"data": { "value": 83.3, "passed": 10, "executed": 12, "closedRuns": 2 }
}

Un widget time_series devuelve range, recortado a la retención de tu plan igual que un widget created_count, dividido en intervalos:

{
"type": "time_series",
"widgetUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BG",
"metric": "run_pass_rate",
"scope": { "includedProjectCount": 2, "hiddenProjectCount": 0, "emptySelection": false },
"range": {
"requestedDays": 7,
"effectiveDays": 7,
"from": 1759147200000,
"to": 1759752000000,
"clampedByRetention": false
},
"data": {
"granularity": "day",
"timeZone": "America/Argentina/Buenos_Aires",
"buckets": [
{ "start": 1759147200000, "end": 1759201200000 },
{ "start": 1759201200000, "end": 1759287600000 }
],
"series": [{ "key": "passRate", "values": [null, 83.3] }]
}
}

(Abreviado: un rango de 7 días tiene 8 intervalos.)

  • granularity es day para rangos de hasta 31 días, week hasta 186 días (las semanas empiezan el lunes) y month para rangos más largos. Un heatmap es siempre day.
  • Los límites de los intervalos caen a la medianoche de timeZone: la zona horaria de tu perfil, o UTC si no elegiste ninguna. Dos personas pueden ver límites distintos en el mismo widget. Cuando cambia el horario de verano, un intervalo puede durar una hora más o una hora menos.
  • buckets son ventanas [start, end) contiguas, en milisegundos desde epoch; la primera empieza en range.from y la última termina en range.to, así que ambas pueden ser parciales.
  • series tiene un valor por intervalo, en el mismo orden, para cada serie de la métrica:
metricSeriesValor por intervalo
open_defectsopenDefectos abiertos (open o in_progress) al final del intervalo. Los defectos eliminados nunca se cuentan
result_eventspassed, failed, skipped, blockedResultados registrados en el intervalo con ese estado, según su hora de ejecución
case_eventscreated, editedCasos de prueba creados, y ediciones guardadas: un guardado, una importación o una restauración. Los cambios hechos a través de un paso compartido no cuentan como ediciones
run_pass_ratepassRateDe las ejecuciones cerradas en el intervalo (sin las abortadas), casos aprobados ÷ casos ejecutados, como porcentaje con un decimal. null si no se ejecutó nada
run_average_durationaverageDurationMsTiempo promedio entre el inicio y el cierre de las ejecuciones cerradas en el intervalo (sin las abortadas; una ejecución cerrada antes de su inicio cuenta como 0). null si no se cerró ninguna

La tasa de aprobación suma los casos de todas las ejecuciones del intervalo antes de dividir: una ejecución con 100 casos ejecutados pesa más que una con 2. Los casos ejecutados son los aprobados, fallidos, bloqueados y omitidos; no cuentan los casos sin probar ni los que esperan volver a probarse.

Un section_title no tiene datos y devuelve 422 validation_failed. Un widget que no existe, o un dashboard que no puedes ver, devuelve 404 not_found.

CampoTipoNotas
ulidstringEl ULID del widget
typestringConsulta Catálogo
titlestring | nullSiempre es un string en un section_title
widthstringthird, half, two_thirds o full
heightnumberFilas de la cuadrícula, de 2 a 8; no aparece en un section_title
positionnumberLugar en la cuadrícula, desde 0
campos de configuración—Los del tipo del widget, por ejemplo metric y rangeDays
projectsobject | nullnull en un section_title
projects.modestringall o selected
projects.projectUlidsstring[]Los proyectos seleccionados que tú puedes abrir; vacío con all
projects.hiddenProjectCountnumberProyectos seleccionados que no puedes abrir
filtersobject[]No aparece en un section_title. Consulta Filtros
filters[].values[].labelstring | nullVer más abajo
createdAt, updatedAtnumberMilisegundos desde epoch; reordenar no cambia updatedAt

Cada filtro de una respuesta repite field (y customFieldUlid, además de customFieldTitle y customFieldType, ambos null cuando el campo se eliminó) y lista sus valores como { "value", "label" }. label es el nombre del miembro, o el nombre de la suite, etiqueta, hito, entorno, plan de prueba u opción; los títulos de campos personalizados y los nombres de opciones siguen el idioma de la solicitud. Es null para un estado o un valor de casilla, para una entidad eliminada y para una entidad de un proyecto que no puedes abrir.

Cada cambio en un widget también actualiza el updatedAt del dashboard.

GET/api/v1/dashboards/{dashboardUlid}/widgets/{widgetUlid}/records

Lista los registros detrás de un valor de un widget, de a una página por vez: los casos, defectos, ejecuciones, resultados o suites que cuenta el número. Sigue las mismas reglas que los datos del widget. Cualquiera que pueda ver el dashboard puede leerlos, y solo se listan los proyectos del alcance del widget que puedes abrir. El total es el valor que muestran los datos del widget para el mismo selector. En run_pass_rate y run_average_duration, total es la cantidad de ejecuciones detrás del punto, no la tasa de aprobación ni el promedio graficado.

Elige el valor con el selector que admite el widget:

WidgetSelectorLista
count, created_countninguno: el valor completolos registros que cuenta la métrica
distributionbucket: una key de data.buckets (el ULID de una opción o de una persona, true, false, not_set u other)los casos de ese grupo
time_seriesbucketStart: un start de data.buckets, y series cuando la métrica tiene varias (result_events: passed, failed, skipped o blocked; case_events: created o edited)los registros de ese intervalo y de esa serie

En time_series, una métrica con una sola serie admite su propio nombre de serie, de forma opcional: open_defects admite open, run_pass_rate admite passRate y run_average_duration admite averageDurationMs. La serie se verifica con la métrica y no cambia nada; cualquier otra serie devuelve 422 validation_failed. En un widget heatmap, bucketStart nombra un día local, y series sigue las mismas reglas. Una celda cuenta todas las series juntas, así que los registros de una serie son solo una parte del recuento de ese día; la aplicación web no abre los registros de una celda.

GET /api/v1/dashboards/01J9Z3Q4M8R2T6V1W5X7Y9A0BD/widgets/01J9Z3Q4M8R2T6V1W5X7Y9A0BG/records?bucket=not_set&page=1&pageSize=50
{
"kind": "test_case",
"total": 2,
"page": 1,
"pageSize": 50,
"items": [
{
"kind": "test_case",
"projectUlid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BK",
"projectCode": "PAY",
"projectName": "Payments",
"ulid": "01J9Z3Q4M8R2T6V1W5X7Y9A0BM",
"displayId": "PAY-41",
"title": "Reembolsar una tarjeta",
"suiteName": "Compra",
"archived": false,
"createdAt": 1759600000000,
"revision": null
}
]
}
  • kind es el tipo de cada elemento: test_case, defect, run, result o suite.
  • Cada elemento trae lo que una fila necesita para mostrar y enlazar: su proyecto (projectUlid, projectCode, projectName), su ulid y su displayId. El identificador visible de un caso es el código de su proyecto y su número (PAY-41); el de un defecto es D- y su número; el de una ejecución es R- y su número; el de una suite es el código de su proyecto, S y su número (PAY-S4).
  • Los elementos van del más reciente al más antiguo según la marca de tiempo que usa la métrica; si empatan, por ID. Las páginas empiezan en 1; pageSize vale 50 por defecto y como máximo 200.

Los campos según el tipo:

kindCampos
test_casetitle, suiteName, archived, createdAt, y revision (number, source, savedAt) en un registro edited de case_events; en los demás es null
defecttitle, status (el estado actual: un defecto que estaba abierto en el pasado pudo cerrarse después), createdAt, closedAt
runname, state, status, startedAt, closedAt, passed, executed, passRate (null si no se ejecutó nada), durationMs (null mientras la ejecución está abierta)
resultstatus, executedAt, executedByName (null si la organización ya no conoce a quien lo ejecutó), runUlid, runDisplayId, runCaseUlid (null si el caso ya no está en la ejecución), testCaseUlid, testCaseDisplayId, testCaseTitle (el título con el que se ejecutó; los campos del caso son null una vez eliminado)
suitename, createdAt

Un porcentaje de aprobación es la suma de passed de los elementos dividida entre la suma de executed, tal como lo calcula la serie. Un tiempo promedio es la media de durationMs de los elementos.

Un bucketStart identifica el intervalo del día, la semana o el mes local en el que cae. El primer intervalo empieza al inicio del rango, que se mueve con el tiempo, así que un inicio tomado de una respuesta anterior sigue encontrándolo. En una distribución de select_multi, un caso que tiene dos opciones de other es un solo registro, igual que en el conteo del grupo.

Errores:

  • 422 validation_failed cuando el selector no corresponde al widget (un bucket en un count, o una serie temporal sin bucketStart), y para un section_title, que no tiene registros.
  • 404 not_found cuando el grupo no es uno que muestre el widget (una opción agrupada en other), cuando el intervalo no es uno que muestre (un bucketStart anterior al rango), y para un widget o un dashboard que no puedes ver.
  • 403 forbidden cuando el plan no incluye dashboards.

Solo lectura: no se registra ningún evento de auditoría.

Cada cambio registra un evento sobre el dashboard (tipo de entidad dashboard, con su nombre como etiqueta) con el widgetUlid y el type del widget en la metadata: dashboard.widget_added, dashboard.widget_updated (además de changedFields, los nombres de los campos que cambiaron, como title, width, height, projects, rangeDays o filters) y dashboard.widget_removed. Un reordenamiento registra dashboard.widgets_reordered con widgetUlids en su nuevo orden. Un cambio que no cambia nada no registra nada. Consulta Eventos de auditoría.

Las mismas operaciones están disponibles como las herramientas MCP list_dashboard_widgets, create_dashboard_widget, update_dashboard_widget, reorder_dashboard_widgets, delete_dashboard_widget, get_dashboard_widget_data y list_dashboard_widget_records, con las mismas reglas. Consulta la referencia de herramientas MCP.