Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboards] Move reference injection/extraction from client to the server #192758

Open
nickpeihl opened this issue Sep 12, 2024 · 0 comments
Open
Assignees
Labels
Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas

Comments

@nickpeihl
Copy link
Member

nickpeihl commented Sep 12, 2024

Dashboards often use references to link to another saved object, such as a Lens visualization that might be saved in the Library. In runtime state, the Dashboard includes the Library item’s saved object id in the panels state. When saving the Dashboard, the panels state is serialized and any saved object ids in panel state are moved into a top level references array in the saved object. This process is called reference extraction.

Conversely, when opening a Dashboard from a saved object, the saved object ids in the references array must be moved into panel state. This is called reference injection.

Reference extraction and injection are currently handled in the UI. As part of #192621, we'd like to move reference handling to the server. This will simplify content generation for the UI and consumers of the API as reference extraction and injection are handled at the Content Management layer.

@nickpeihl nickpeihl added the Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas label Sep 12, 2024
@nickpeihl nickpeihl self-assigned this Sep 12, 2024
nickpeihl added a commit that referenced this issue Nov 8, 2024
Closes #[192618](#192618)

Adds public CRUD+List endpoints for the Dashboards API. 

The schema for the endpoints are generated from Content Management
schemas so that the RPC and Public APIs use the same schemas for CRUD
operations. A new version (v3) has been added to the Dashboards content
management specification that decouples Content from Saved Objects using
a translation layer in Content Management. When retrieving a saved
object the Content Management layer parses and validates the panelJSON,
optionsListJSON, and savedSearchJSON properties against the defines
schema and passes the translated content to the consumer (user interface
or API).

When writing a saved object, the Content Management layer serializes
(`JSON.stringify`) the Content object into the saved object schema. So
the saved object schema continues to store as stringified JSON, but the
user interface and public API see and use the JSON objects.

These planned features are out of scope for this PR and may be added in
subsequent PRs.
1) #192758
2) #192622

Reviewers, please test both UI and endpoints. 

# cURL examples: 

First, `yarn start --no-base-path`. Assumes `elastic:changeme` is the
username:password.

## Create

<details>
<summary>Create an empty dashboard with the minimum required
properties</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "attributes": { "title": "my empty dashboard" }
  }'
```

</details>

<details>
<summary>Create a dashboard of a specific ID with some ES|QL
panels</summary>


```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "description": "",
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                  "name": "kibana_sample_data_ecommerce",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "title": "kibana_sample_data_ecommerce",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "title": "kibana_sample_data_ecommerce"
                    }
                  ],
                  "layers": {
                    "44866844-8fca-482a-a769-006e7d029b9b": {
                      "columns": [
                        {
                          "columnId": "6376af5c-fdd1-4d72-a3ec-5686b5049664",
                          "fieldName": "customer_gender",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        },
                        {
                          "columnId": "a2e3e039-dff6-4893-9c9d-9f0a816207dd",
                          "fieldName": "taxless_total_price",
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        }
                      ],
                      "index": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "query": {
                        "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
                      }
                    },
                    "781db49e-f4f1-42e0-975f-7118d2ef7a18": {
                      "columns": [],
                      "index": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "query": {
                        "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
                      }
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
              },
              "visualization": {
                "layers": [
                  {
                    "categoryDisplay": "default",
                    "colorMapping": {
                      "assignments": [],
                      "colorMode": {
                        "type": "categorical"
                      },
                      "paletteId": "eui_amsterdam_color_blind",
                      "specialAssignments": [
                        {
                          "color": {
                            "type": "loop"
                          },
                          "rule": {
                            "type": "other"
                          },
                          "touched": false
                        }
                      ]
                    },
                    "layerId": "44866844-8fca-482a-a769-006e7d029b9b",
                    "layerType": "data",
                    "legendDisplay": "default",
                    "metrics": [
                      "a2e3e039-dff6-4893-9c9d-9f0a816207dd"
                    ],
                    "nestedLegend": false,
                    "numberDisplay": "percent",
                    "primaryGroups": [
                      "6376af5c-fdd1-4d72-a3ec-5686b5049664"
                    ]
                  }
                ],
                "shape": "pie"
              }
            },
            "title": "Table category & category.keyword & currency & customer_first_name & customer_first_name.keyword",
            "type": "lens",
            "visualizationType": "lnsPie"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 0,
          "y": 0
        },
        "type": "lens"
      },
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                  "name": "kibana_sample_data_logs",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "timeFieldName": "@timestamp",
                  "title": "kibana_sample_data_logs",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                      "timeField": "@timestamp",
                      "title": "kibana_sample_data_logs"
                    }
                  ],
                  "layers": {
                    "2e3f211d-289f-4a24-87bb-1ccacd678adb": {
                      "columns": [
                        {
                          "columnId": "AVG(machine.ram)",
                          "fieldName": "AVG(machine.ram)",
                          "inMetricDimension": true,
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        },
                        {
                          "columnId": "machine.os.keyword",
                          "fieldName": "machine.os.keyword",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        }
                      ],
                      "index": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                      "query": {
                        "esql": "FROM kibana_sample_data_logs| STATS AVG(machine.ram) BY machine.os.keyword "
                      },
                      "timeField": "@timestamp"
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_logs| STATS AVG(machine.ram) BY machine.os.keyword "
              },
              "visualization": {
                "axisTitlesVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "fittingFunction": "None",
                "gridlinesVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "labelsOrientation": {
                  "x": 0,
                  "yLeft": 0,
                  "yRight": 0
                },
                "layers": [
                  {
                    "accessors": [
                      "AVG(machine.ram)"
                    ],
                    "colorMapping": {
                      "assignments": [],
                      "colorMode": {
                        "type": "categorical"
                      },
                      "paletteId": "eui_amsterdam_color_blind",
                      "specialAssignments": [
                        {
                          "color": {
                            "type": "loop"
                          },
                          "rule": {
                            "type": "other"
                          },
                          "touched": false
                        }
                      ]
                    },
                    "layerId": "2e3f211d-289f-4a24-87bb-1ccacd678adb",
                    "layerType": "data",
                    "seriesType": "bar_stacked",
                    "xAccessor": "machine.os.keyword"
                  }
                ],
                "legend": {
                  "isVisible": true,
                  "position": "right"
                },
                "preferredSeriesType": "bar_stacked",
                "tickLabelsVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "valueLabels": "hide"
              }
            },
            "title": "Bar vertical stacked",
            "type": "lens",
            "visualizationType": "lnsXY"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 24,
          "y": 0
        },
        "type": "lens"
      },
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                  "name": "kibana_sample_data_flights",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "title": "kibana_sample_data_flights",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                      "title": "kibana_sample_data_flights"
                    }
                  ],
                  "layers": {
                    "4451c40f-b3ef-464e-b3d4-b10469f65c2a": {
                      "columns": [
                        {
                          "columnId": "AvgDelayMins",
                          "fieldName": "AvgDelayMins",
                          "inMetricDimension": true,
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        },
                        {
                          "columnId": "Carrier",
                          "fieldName": "Carrier",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        }
                      ],
                      "index": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                      "query": {
                        "esql": "FROM kibana_sample_data_flights| STATS AvgDelayMins = AVG(FlightDelayMin) BY Carrier "
                      }
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_flights| STATS AvgDelayMins = AVG(FlightDelayMin) BY Carrier "
              },
              "visualization": {
                "breakdownByAccessor": "Carrier",
                "layerId": "4451c40f-b3ef-464e-b3d4-b10469f65c2a",
                "layerType": "data",
                "metricAccessor": "AvgDelayMins",
                "palette": {
                  "name": "status",
                  "params": {
                    "colorStops": [],
                    "continuity": "all",
                    "maxSteps": 5,
                    "name": "status",
                    "progression": "fixed",
                    "rangeMax": 100,
                    "rangeMin": 0,
                    "rangeType": "percent",
                    "reverse": false,
                    "steps": 3,
                    "stops": [
                      {
                        "color": "#209280",
                        "stop": 33.33
                      },
                      {
                        "color": "#d6bf57",
                        "stop": 66.66
                      },
                      {
                        "color": "#cc5642",
                        "stop": 100
                      }
                    ]
                  },
                  "type": "palette"
                }
              }
            },
            "title": "Bar vertical stacked",
            "type": "lens",
            "visualizationType": "lnsMetric"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 0,
          "y": 15
        },
        "type": "lens"
      }
    ],
    "timeRestore": false,
    "title": "several es|ql panels",
    "version": 3
  }
}'
```
</details>

<details>
<summary>Create a dashboard with a Links panel</summary>


```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "layout": "vertical",
            "links": [
              {
                "destinationRefName": "link_1981a00f-8120-4c80-b37f-ed38969afe09_dashboard",
                "id": "1981a00f-8120-4c80-b37f-ed38969afe09",
                "order": 0,
                "type": "dashboardLink"
              },
              {
                "destinationRefName": "link_f2e1a75c-fbca-4f41-a290-d5d89a60a797_dashboard",
                "id": "f2e1a75c-fbca-4f41-a290-d5d89a60a797",
                "order": 1,
                "type": "dashboardLink"
              },
              {
                "destination": "https://example.com",
                "id": "63342ea6-f686-42b2-a526-ec0bcf4476b0",
                "order": 2,
                "type": "externalLink"
              }
            ]
          },
          "enhancements": {},
          "id": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b"
        },
        "gridData": {
          "h": 7,
          "i": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b",
          "w": 8,
          "x": 0,
          "y": 0
        },
        "panelIndex": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b",
        "type": "links"
      }
    ],
    "timeRestore": false,
    "title": "a links panel",
    "version": 3
  },
  "references": [
    {
      "id": "722b74f0-b882-11e8-a6d9-e546fe2bba5f",
      "name": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b:link_1981a00f-8120-4c80-b37f-ed38969afe09_dashboard",
      "type": "dashboard"
    },
    {
      "id": "edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b",
      "name": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b:link_f2e1a75c-fbca-4f41-a290-d5d89a60a797_dashboard",
      "type": "dashboard"
    }
  ]
}'
```
</details>

<details>
<summary>Create a dashboard with a Maps panel</summary>


```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "description": "",
            "layerListJSON": "[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"db63eee8-3dfc-48c6-8c8b-7f2c4e32329d\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"},{\"sourceDescriptor\":{\"geoField\":\"geoip.location\",\"scalingType\":\"MVT\",\"id\":\"9ee192e4-18f0-41b2-b8b7-89eb91d0e529\",\"type\":\"ES_SEARCH\",\"applyGlobalQuery\":true,\"applyGlobalTime\":true,\"applyForceRefresh\":true,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"topHitsGroupByTimeseries\":false,\"topHitsSplitField\":\"\",\"topHitsSize\":1,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"65710bbc-f41c-4fe7-b0c3-a6dbc0613220\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"category.keyword\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":3},\"type\":\"CATEGORICAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":0}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelZoomRange\":{\"options\":{\"useLayerZoomRange\":true,\"minZoom\":0,\"maxZoom\":24}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}},\"labelPosition\":{\"options\":{\"position\":\"CENTER\"}}},\"isTimeAware\":true},\"includeInFitToBounds\":true,\"type\":\"MVT_VECTOR\",\"joins\":[],\"disableTooltips\":false}]",
            "mapStateJSON": "{\"adHocDataViews\":[],\"zoom\":1.57,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"disabled\":false,\"negate\":false,\"alias\":\"males only\",\"index\":\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\",\"key\":\"customer_gender\",\"field\":\"customer_gender\",\"params\":{\"query\":\"MALE\"},\"type\":\"phrase\"},\"query\":{\"match_phrase\":{\"customer_gender\":\"MALE\"}},\"$state\":{\"store\":\"appState\"}}],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
            "title": "",
            "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"65710bbc-f41c-4fe7-b0c3-a6dbc0613220\"]}"
          },
          "enhancements": {
            "dynamicActions": {
              "events": []
            }
          },
          "hiddenLayers": [],
          "id": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
          "isLayerTOCOpen": false,
          "mapBuffer": {
            "maxLat": 85.05113,
            "maxLon": 180,
            "minLat": -66.51326,
            "minLon": -180
          },
          "mapCenter": {
            "lat": 19.94277,
            "lon": 0,
            "zoom": 1.57
          },
          "openTOCDetails": [
            "65710bbc-f41c-4fe7-b0c3-a6dbc0613220"
          ]
        },
        "gridData": {
          "h": 25,
          "i": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
          "w": 38,
          "x": 0,
          "y": 0
        },
        "panelIndex": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
        "type": "map"
      }
    ],
    "timeRestore": false,
    "title": "a maps panel",
    "version": 3
  },
  "references": [
    {
      "type": "tag",
      "id": "662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "name": "tag-ref-662b28f2-71e4-4c04-b4e5-0c6249b1c08a"
    },
    {
      "name": "108b2f72-0101-4e09-b8a9-22f7aa9573b0:layer_1_source_index_pattern",
      "type": "index-pattern",
      "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f"
    }
  ]
}'
```

</details>

<details>
<summary>Create a dashboard with a Filter pill and a Field statistics
panel</summary>


```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "description": "",
    "kibanaSavedObjectMeta": {
      "searchSource": {
        "filter": [
          {
            "$state": {
              "store": "appState"
            },
            "meta": {
              "alias": "gnomehouse",
              "disabled": false,
              "field": "products.manufacturer.keyword",
              "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
              "key": "products.manufacturer.keyword",
              "negate": false,
              "params": [
                "Gnomehouse",
                "Gnomehouse mom"
              ],
              "type": "phrases"
            },
            "query": {
              "bool": {
                "minimum_should_match": 1,
                "should": [
                  {
                    "match_phrase": {
                      "products.manufacturer.keyword": "Gnomehouse"
                    }
                  },
                  {
                    "match_phrase": {
                      "products.manufacturer.keyword": "Gnomehouse mom"
                    }
                  }
                ]
              }
            }
          }
        ],
        "query": {
          "language": "kuery",
          "query": ""
        }
      }
    },
    "panels": [
      {
        "panelConfig": {
          "dataViewId": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
          "enhancements": {},
          "id": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
          "query": {
            "esql": "from kibana_sample_data_ecommerce | limit 10"
          },
          "viewType": "esql"
        },
        "gridData": {
          "h": 18,
          "i": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
          "w": 48,
          "x": 0,
          "y": 0
        },
        "panelIndex": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
        "type": "field_stats_table"
      }
    ],
    "timeRestore": false,
    "title": "field stats panel",
    "version": 2
  },
  "references": [
    {
      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
      "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
      "type": "index-pattern"
    },
    {
      "id": "662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "name": "tag-ref-662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "type": "tag"
    },
    {
      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
      "name": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4:fieldStatsTableDataViewId",
      "type": "index-pattern"
    }
  ]
}'
```
</details>

<details>
<summary>Create a dashboard with a Lens panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard/' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "title": "a lens panel",
    "kibanaSavedObjectMeta": {
      "searchSource": {}
    },
    "timeRestore": false,
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "title": "",
            "visualizationType": "lnsDatatable",
            "type": "lens",
            "references": [
              {
                "type": "index-pattern",
                "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d",
                "name": "indexpattern-datasource-layer-b9789655-f916-4732-9bf2-641a88075210"
              }
            ],
            "state": {
              "visualization": {
                "layerId": "b9789655-f916-4732-9bf2-641a88075210",
                "layerType": "data",
                "columns": [
                  {
                    "isTransposed": false,
                    "columnId": "4175e737-76b9-46db-894b-57106a06b9cb"
                  },
                  {
                    "isTransposed": false,
                    "columnId": "1494f183-3bfa-4602-a780-6a41624f6c69"
                  },
                  {
                    "isTransposed": false,
                    "columnId": "8eb92ea9-5b76-45a2-865e-d78511c1e506"
                  }
                ]
              },
              "query": {
                "query": "",
                "language": "kuery"
              },
              "filters": [],
              "datasourceStates": {
                "formBased": {
                  "layers": {
                    "b9789655-f916-4732-9bf2-641a88075210": {
                      "columns": {
                        "4175e737-76b9-46db-894b-57106a06b9cb": {
                          "label": "Top 5 values of Carrier",
                          "dataType": "string",
                          "operationType": "terms",
                          "scale": "ordinal",
                          "sourceField": "Carrier",
                          "isBucketed": true,
                          "params": {
                            "size": 5,
                            "orderBy": {
                              "type": "column",
                              "columnId": "1494f183-3bfa-4602-a780-6a41624f6c69"
                            },
                            "orderDirection": "desc",
                            "otherBucket": true,
                            "missingBucket": false,
                            "parentFormat": {
                              "id": "terms"
                            },
                            "include": [],
                            "exclude": [],
                            "includeIsRegex": false,
                            "excludeIsRegex": false
                          }
                        },
                        "1494f183-3bfa-4602-a780-6a41624f6c69": {
                          "label": "Count of records",
                          "dataType": "number",
                          "operationType": "count",
                          "isBucketed": false,
                          "scale": "ratio",
                          "sourceField": "___records___",
                          "params": {
                            "emptyAsNull": true
                          }
                        },
                        "8eb92ea9-5b76-45a2-865e-d78511c1e506": {
                          "label": "Median of AvgTicketPrice",
                          "dataType": "number",
                          "operationType": "median",
                          "sourceField": "AvgTicketPrice",
                          "isBucketed": false,
                          "scale": "ratio",
                          "params": {
                            "emptyAsNull": true
                          }
                        }
                      },
                      "columnOrder": [
                        "4175e737-76b9-46db-894b-57106a06b9cb",
                        "1494f183-3bfa-4602-a780-6a41624f6c69",
                        "8eb92ea9-5b76-45a2-865e-d78511c1e506"
                      ],
                      "incompleteColumns": {},
                      "sampling": 1
                    }
                  }
                },
                "indexpattern": {
                  "layers": {}
                },
                "textBased": {
                  "layers": {}
                }
              },
              "internalReferences": [],
              "adHocDataViews": {}
            }
          },
          "enhancements": {}
        },
        "gridData": {
          "x": 0,
          "y": 0,
          "w": 24,
          "h": 15
        },
        "type": "lens"
      }
    ],
    "options": {
      "hidePanelTitles": false,
      "useMargins": true,
      "syncColors": false,
      "syncTooltips": true,
      "syncCursor": true
    },
    "version": 3
  },
  "references": [
    {
      "type": "index-pattern",
      "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d",
      "name": "indexpattern-datasource-layer-b9789655-f916-4732-9bf2-641a88075210"
    }
  ]
}'
```

</details>


<details>
<summary>Create a dashboard in a specific Space</summary>

```
curl  -X POST \
  'http://localhost:5601/s/space-1/api/dashboards/dashboard/' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
  "title": "my other demo dashboard",
  "kibanaSavedObjectMeta": {
    "searchSource": {}
  },
  "timeRestore": false,
  "panels": [
    {
      "panelConfig": {
        "savedVis": {
          "description": "",
          "type": "markdown",
          "params": {
            "fontSize": 12,
            "openLinksInNewTab": false,
            "markdown": "## Sample eCommerce Data\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."
          },
          "uiState": {},
          "data": {
            "aggs": [],
            "searchSource": {
              "query": {
                "query": "",
                "language": "kuery"
              },
              "filter": []
            }
          }
        },
        "enhancements": {}
      },
      "gridData": {
        "x": 0,
        "y": 0,
        "w": 24,
        "h": 15,
        "i": "1"
      },
      "type": "visualization",
      "version": "7.9.2"
    }
  ],
  "options": {
    "hidePanelTitles": false,
    "useMargins": true,
    "syncColors": false,
    "syncTooltips": true,
    "syncCursor": true
  },
  "version": 3
  },
  "references": [],
  "spaces": ["space-1"]
}'
```

</details>

## Update

<details>
<summary>Update an existing dashboard</summary>

```
curl  -X PUT \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "title": "my demo dashboard",
    "kibanaSavedObjectMeta": {
      "searchSource": {}
    },
    "timeRestore": false,
    "panels": [
      {
        "panelConfig": {
          "savedVis": {
            "description": "",
            "type": "markdown",
            "params": {
              "fontSize": 12,
              "openLinksInNewTab": false,
              "markdown": "## Sample eCommerce Data\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html).\nWubba lubba dub-dub!"
            },
            "uiState": {},
            "data": {
              "aggs": [],
              "searchSource": {
                "query": {
                  "query": "",
                  "language": "kuery"
                },
                "filter": []
              }
            }
          },
          "enhancements": {}
        },
        "gridData": {
          "x": 0,
          "y": 0,
          "w": 24,
          "h": 15
        },
        "type": "visualization"
      }
    ],
    "version": 3
  },
  "references": []
}'
```

</details>

## Get / List

<details>
<summary>Get a dashboard</summary>

```
curl  -X GET \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
    --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' 
```
</details>

<details>
<summary>Get a paginated list of dashboards</summary>


```
curl  -X GET \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' 
```
</details>

## Delete
<details>
<summary>Delete a dashboard</summary>

```
curl  -X DELETE \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' 
```

</details>

## Open API specification

<details>
<summary>Retrieve the Open API specification</summary>

```
curl  -X GET \
  'http://localhost:5601/api/oas?pathStartsWith=%2Fapi%2Fdashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' 
```

</details>

---------

Co-authored-by: kibanamachine <[email protected]>
kibanamachine pushed a commit to kibanamachine/kibana that referenced this issue Nov 8, 2024
Closes #[192618](elastic#192618)

Adds public CRUD+List endpoints for the Dashboards API.

The schema for the endpoints are generated from Content Management
schemas so that the RPC and Public APIs use the same schemas for CRUD
operations. A new version (v3) has been added to the Dashboards content
management specification that decouples Content from Saved Objects using
a translation layer in Content Management. When retrieving a saved
object the Content Management layer parses and validates the panelJSON,
optionsListJSON, and savedSearchJSON properties against the defines
schema and passes the translated content to the consumer (user interface
or API).

When writing a saved object, the Content Management layer serializes
(`JSON.stringify`) the Content object into the saved object schema. So
the saved object schema continues to store as stringified JSON, but the
user interface and public API see and use the JSON objects.

These planned features are out of scope for this PR and may be added in
subsequent PRs.
1) elastic#192758
2) elastic#192622

Reviewers, please test both UI and endpoints.

# cURL examples:

First, `yarn start --no-base-path`. Assumes `elastic:changeme` is the
username:password.

## Create

<details>
<summary>Create an empty dashboard with the minimum required
properties</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "attributes": { "title": "my empty dashboard" }
  }'
```

</details>

<details>
<summary>Create a dashboard of a specific ID with some ES|QL
panels</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "description": "",
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                  "name": "kibana_sample_data_ecommerce",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "title": "kibana_sample_data_ecommerce",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "title": "kibana_sample_data_ecommerce"
                    }
                  ],
                  "layers": {
                    "44866844-8fca-482a-a769-006e7d029b9b": {
                      "columns": [
                        {
                          "columnId": "6376af5c-fdd1-4d72-a3ec-5686b5049664",
                          "fieldName": "customer_gender",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        },
                        {
                          "columnId": "a2e3e039-dff6-4893-9c9d-9f0a816207dd",
                          "fieldName": "taxless_total_price",
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        }
                      ],
                      "index": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "query": {
                        "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
                      }
                    },
                    "781db49e-f4f1-42e0-975f-7118d2ef7a18": {
                      "columns": [],
                      "index": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "query": {
                        "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
                      }
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
              },
              "visualization": {
                "layers": [
                  {
                    "categoryDisplay": "default",
                    "colorMapping": {
                      "assignments": [],
                      "colorMode": {
                        "type": "categorical"
                      },
                      "paletteId": "eui_amsterdam_color_blind",
                      "specialAssignments": [
                        {
                          "color": {
                            "type": "loop"
                          },
                          "rule": {
                            "type": "other"
                          },
                          "touched": false
                        }
                      ]
                    },
                    "layerId": "44866844-8fca-482a-a769-006e7d029b9b",
                    "layerType": "data",
                    "legendDisplay": "default",
                    "metrics": [
                      "a2e3e039-dff6-4893-9c9d-9f0a816207dd"
                    ],
                    "nestedLegend": false,
                    "numberDisplay": "percent",
                    "primaryGroups": [
                      "6376af5c-fdd1-4d72-a3ec-5686b5049664"
                    ]
                  }
                ],
                "shape": "pie"
              }
            },
            "title": "Table category & category.keyword & currency & customer_first_name & customer_first_name.keyword",
            "type": "lens",
            "visualizationType": "lnsPie"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 0,
          "y": 0
        },
        "type": "lens"
      },
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                  "name": "kibana_sample_data_logs",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "timeFieldName": "@timestamp",
                  "title": "kibana_sample_data_logs",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                      "timeField": "@timestamp",
                      "title": "kibana_sample_data_logs"
                    }
                  ],
                  "layers": {
                    "2e3f211d-289f-4a24-87bb-1ccacd678adb": {
                      "columns": [
                        {
                          "columnId": "AVG(machine.ram)",
                          "fieldName": "AVG(machine.ram)",
                          "inMetricDimension": true,
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        },
                        {
                          "columnId": "machine.os.keyword",
                          "fieldName": "machine.os.keyword",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        }
                      ],
                      "index": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                      "query": {
                        "esql": "FROM kibana_sample_data_logs| STATS AVG(machine.ram) BY machine.os.keyword "
                      },
                      "timeField": "@timestamp"
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_logs| STATS AVG(machine.ram) BY machine.os.keyword "
              },
              "visualization": {
                "axisTitlesVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "fittingFunction": "None",
                "gridlinesVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "labelsOrientation": {
                  "x": 0,
                  "yLeft": 0,
                  "yRight": 0
                },
                "layers": [
                  {
                    "accessors": [
                      "AVG(machine.ram)"
                    ],
                    "colorMapping": {
                      "assignments": [],
                      "colorMode": {
                        "type": "categorical"
                      },
                      "paletteId": "eui_amsterdam_color_blind",
                      "specialAssignments": [
                        {
                          "color": {
                            "type": "loop"
                          },
                          "rule": {
                            "type": "other"
                          },
                          "touched": false
                        }
                      ]
                    },
                    "layerId": "2e3f211d-289f-4a24-87bb-1ccacd678adb",
                    "layerType": "data",
                    "seriesType": "bar_stacked",
                    "xAccessor": "machine.os.keyword"
                  }
                ],
                "legend": {
                  "isVisible": true,
                  "position": "right"
                },
                "preferredSeriesType": "bar_stacked",
                "tickLabelsVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "valueLabels": "hide"
              }
            },
            "title": "Bar vertical stacked",
            "type": "lens",
            "visualizationType": "lnsXY"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 24,
          "y": 0
        },
        "type": "lens"
      },
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                  "name": "kibana_sample_data_flights",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "title": "kibana_sample_data_flights",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                      "title": "kibana_sample_data_flights"
                    }
                  ],
                  "layers": {
                    "4451c40f-b3ef-464e-b3d4-b10469f65c2a": {
                      "columns": [
                        {
                          "columnId": "AvgDelayMins",
                          "fieldName": "AvgDelayMins",
                          "inMetricDimension": true,
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        },
                        {
                          "columnId": "Carrier",
                          "fieldName": "Carrier",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        }
                      ],
                      "index": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                      "query": {
                        "esql": "FROM kibana_sample_data_flights| STATS AvgDelayMins = AVG(FlightDelayMin) BY Carrier "
                      }
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_flights| STATS AvgDelayMins = AVG(FlightDelayMin) BY Carrier "
              },
              "visualization": {
                "breakdownByAccessor": "Carrier",
                "layerId": "4451c40f-b3ef-464e-b3d4-b10469f65c2a",
                "layerType": "data",
                "metricAccessor": "AvgDelayMins",
                "palette": {
                  "name": "status",
                  "params": {
                    "colorStops": [],
                    "continuity": "all",
                    "maxSteps": 5,
                    "name": "status",
                    "progression": "fixed",
                    "rangeMax": 100,
                    "rangeMin": 0,
                    "rangeType": "percent",
                    "reverse": false,
                    "steps": 3,
                    "stops": [
                      {
                        "color": "#209280",
                        "stop": 33.33
                      },
                      {
                        "color": "#d6bf57",
                        "stop": 66.66
                      },
                      {
                        "color": "#cc5642",
                        "stop": 100
                      }
                    ]
                  },
                  "type": "palette"
                }
              }
            },
            "title": "Bar vertical stacked",
            "type": "lens",
            "visualizationType": "lnsMetric"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 0,
          "y": 15
        },
        "type": "lens"
      }
    ],
    "timeRestore": false,
    "title": "several es|ql panels",
    "version": 3
  }
}'
```
</details>

<details>
<summary>Create a dashboard with a Links panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "layout": "vertical",
            "links": [
              {
                "destinationRefName": "link_1981a00f-8120-4c80-b37f-ed38969afe09_dashboard",
                "id": "1981a00f-8120-4c80-b37f-ed38969afe09",
                "order": 0,
                "type": "dashboardLink"
              },
              {
                "destinationRefName": "link_f2e1a75c-fbca-4f41-a290-d5d89a60a797_dashboard",
                "id": "f2e1a75c-fbca-4f41-a290-d5d89a60a797",
                "order": 1,
                "type": "dashboardLink"
              },
              {
                "destination": "https://example.com",
                "id": "63342ea6-f686-42b2-a526-ec0bcf4476b0",
                "order": 2,
                "type": "externalLink"
              }
            ]
          },
          "enhancements": {},
          "id": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b"
        },
        "gridData": {
          "h": 7,
          "i": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b",
          "w": 8,
          "x": 0,
          "y": 0
        },
        "panelIndex": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b",
        "type": "links"
      }
    ],
    "timeRestore": false,
    "title": "a links panel",
    "version": 3
  },
  "references": [
    {
      "id": "722b74f0-b882-11e8-a6d9-e546fe2bba5f",
      "name": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b:link_1981a00f-8120-4c80-b37f-ed38969afe09_dashboard",
      "type": "dashboard"
    },
    {
      "id": "edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b",
      "name": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b:link_f2e1a75c-fbca-4f41-a290-d5d89a60a797_dashboard",
      "type": "dashboard"
    }
  ]
}'
```
</details>

<details>
<summary>Create a dashboard with a Maps panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "description": "",
            "layerListJSON": "[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"db63eee8-3dfc-48c6-8c8b-7f2c4e32329d\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"},{\"sourceDescriptor\":{\"geoField\":\"geoip.location\",\"scalingType\":\"MVT\",\"id\":\"9ee192e4-18f0-41b2-b8b7-89eb91d0e529\",\"type\":\"ES_SEARCH\",\"applyGlobalQuery\":true,\"applyGlobalTime\":true,\"applyForceRefresh\":true,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"topHitsGroupByTimeseries\":false,\"topHitsSplitField\":\"\",\"topHitsSize\":1,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"65710bbc-f41c-4fe7-b0c3-a6dbc0613220\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"category.keyword\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":3},\"type\":\"CATEGORICAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":0}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelZoomRange\":{\"options\":{\"useLayerZoomRange\":true,\"minZoom\":0,\"maxZoom\":24}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}},\"labelPosition\":{\"options\":{\"position\":\"CENTER\"}}},\"isTimeAware\":true},\"includeInFitToBounds\":true,\"type\":\"MVT_VECTOR\",\"joins\":[],\"disableTooltips\":false}]",
            "mapStateJSON": "{\"adHocDataViews\":[],\"zoom\":1.57,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"disabled\":false,\"negate\":false,\"alias\":\"males only\",\"index\":\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\",\"key\":\"customer_gender\",\"field\":\"customer_gender\",\"params\":{\"query\":\"MALE\"},\"type\":\"phrase\"},\"query\":{\"match_phrase\":{\"customer_gender\":\"MALE\"}},\"$state\":{\"store\":\"appState\"}}],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
            "title": "",
            "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"65710bbc-f41c-4fe7-b0c3-a6dbc0613220\"]}"
          },
          "enhancements": {
            "dynamicActions": {
              "events": []
            }
          },
          "hiddenLayers": [],
          "id": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
          "isLayerTOCOpen": false,
          "mapBuffer": {
            "maxLat": 85.05113,
            "maxLon": 180,
            "minLat": -66.51326,
            "minLon": -180
          },
          "mapCenter": {
            "lat": 19.94277,
            "lon": 0,
            "zoom": 1.57
          },
          "openTOCDetails": [
            "65710bbc-f41c-4fe7-b0c3-a6dbc0613220"
          ]
        },
        "gridData": {
          "h": 25,
          "i": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
          "w": 38,
          "x": 0,
          "y": 0
        },
        "panelIndex": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
        "type": "map"
      }
    ],
    "timeRestore": false,
    "title": "a maps panel",
    "version": 3
  },
  "references": [
    {
      "type": "tag",
      "id": "662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "name": "tag-ref-662b28f2-71e4-4c04-b4e5-0c6249b1c08a"
    },
    {
      "name": "108b2f72-0101-4e09-b8a9-22f7aa9573b0:layer_1_source_index_pattern",
      "type": "index-pattern",
      "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f"
    }
  ]
}'
```

</details>

<details>
<summary>Create a dashboard with a Filter pill and a Field statistics
panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "description": "",
    "kibanaSavedObjectMeta": {
      "searchSource": {
        "filter": [
          {
            "$state": {
              "store": "appState"
            },
            "meta": {
              "alias": "gnomehouse",
              "disabled": false,
              "field": "products.manufacturer.keyword",
              "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
              "key": "products.manufacturer.keyword",
              "negate": false,
              "params": [
                "Gnomehouse",
                "Gnomehouse mom"
              ],
              "type": "phrases"
            },
            "query": {
              "bool": {
                "minimum_should_match": 1,
                "should": [
                  {
                    "match_phrase": {
                      "products.manufacturer.keyword": "Gnomehouse"
                    }
                  },
                  {
                    "match_phrase": {
                      "products.manufacturer.keyword": "Gnomehouse mom"
                    }
                  }
                ]
              }
            }
          }
        ],
        "query": {
          "language": "kuery",
          "query": ""
        }
      }
    },
    "panels": [
      {
        "panelConfig": {
          "dataViewId": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
          "enhancements": {},
          "id": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
          "query": {
            "esql": "from kibana_sample_data_ecommerce | limit 10"
          },
          "viewType": "esql"
        },
        "gridData": {
          "h": 18,
          "i": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
          "w": 48,
          "x": 0,
          "y": 0
        },
        "panelIndex": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
        "type": "field_stats_table"
      }
    ],
    "timeRestore": false,
    "title": "field stats panel",
    "version": 2
  },
  "references": [
    {
      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
      "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
      "type": "index-pattern"
    },
    {
      "id": "662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "name": "tag-ref-662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "type": "tag"
    },
    {
      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
      "name": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4:fieldStatsTableDataViewId",
      "type": "index-pattern"
    }
  ]
}'
```
</details>

<details>
<summary>Create a dashboard with a Lens panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard/' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "title": "a lens panel",
    "kibanaSavedObjectMeta": {
      "searchSource": {}
    },
    "timeRestore": false,
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "title": "",
            "visualizationType": "lnsDatatable",
            "type": "lens",
            "references": [
              {
                "type": "index-pattern",
                "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d",
                "name": "indexpattern-datasource-layer-b9789655-f916-4732-9bf2-641a88075210"
              }
            ],
            "state": {
              "visualization": {
                "layerId": "b9789655-f916-4732-9bf2-641a88075210",
                "layerType": "data",
                "columns": [
                  {
                    "isTransposed": false,
                    "columnId": "4175e737-76b9-46db-894b-57106a06b9cb"
                  },
                  {
                    "isTransposed": false,
                    "columnId": "1494f183-3bfa-4602-a780-6a41624f6c69"
                  },
                  {
                    "isTransposed": false,
                    "columnId": "8eb92ea9-5b76-45a2-865e-d78511c1e506"
                  }
                ]
              },
              "query": {
                "query": "",
                "language": "kuery"
              },
              "filters": [],
              "datasourceStates": {
                "formBased": {
                  "layers": {
                    "b9789655-f916-4732-9bf2-641a88075210": {
                      "columns": {
                        "4175e737-76b9-46db-894b-57106a06b9cb": {
                          "label": "Top 5 values of Carrier",
                          "dataType": "string",
                          "operationType": "terms",
                          "scale": "ordinal",
                          "sourceField": "Carrier",
                          "isBucketed": true,
                          "params": {
                            "size": 5,
                            "orderBy": {
                              "type": "column",
                              "columnId": "1494f183-3bfa-4602-a780-6a41624f6c69"
                            },
                            "orderDirection": "desc",
                            "otherBucket": true,
                            "missingBucket": false,
                            "parentFormat": {
                              "id": "terms"
                            },
                            "include": [],
                            "exclude": [],
                            "includeIsRegex": false,
                            "excludeIsRegex": false
                          }
                        },
                        "1494f183-3bfa-4602-a780-6a41624f6c69": {
                          "label": "Count of records",
                          "dataType": "number",
                          "operationType": "count",
                          "isBucketed": false,
                          "scale": "ratio",
                          "sourceField": "___records___",
                          "params": {
                            "emptyAsNull": true
                          }
                        },
                        "8eb92ea9-5b76-45a2-865e-d78511c1e506": {
                          "label": "Median of AvgTicketPrice",
                          "dataType": "number",
                          "operationType": "median",
                          "sourceField": "AvgTicketPrice",
                          "isBucketed": false,
                          "scale": "ratio",
                          "params": {
                            "emptyAsNull": true
                          }
                        }
                      },
                      "columnOrder": [
                        "4175e737-76b9-46db-894b-57106a06b9cb",
                        "1494f183-3bfa-4602-a780-6a41624f6c69",
                        "8eb92ea9-5b76-45a2-865e-d78511c1e506"
                      ],
                      "incompleteColumns": {},
                      "sampling": 1
                    }
                  }
                },
                "indexpattern": {
                  "layers": {}
                },
                "textBased": {
                  "layers": {}
                }
              },
              "internalReferences": [],
              "adHocDataViews": {}
            }
          },
          "enhancements": {}
        },
        "gridData": {
          "x": 0,
          "y": 0,
          "w": 24,
          "h": 15
        },
        "type": "lens"
      }
    ],
    "options": {
      "hidePanelTitles": false,
      "useMargins": true,
      "syncColors": false,
      "syncTooltips": true,
      "syncCursor": true
    },
    "version": 3
  },
  "references": [
    {
      "type": "index-pattern",
      "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d",
      "name": "indexpattern-datasource-layer-b9789655-f916-4732-9bf2-641a88075210"
    }
  ]
}'
```

</details>

<details>
<summary>Create a dashboard in a specific Space</summary>

```
curl  -X POST \
  'http://localhost:5601/s/space-1/api/dashboards/dashboard/' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
  "title": "my other demo dashboard",
  "kibanaSavedObjectMeta": {
    "searchSource": {}
  },
  "timeRestore": false,
  "panels": [
    {
      "panelConfig": {
        "savedVis": {
          "description": "",
          "type": "markdown",
          "params": {
            "fontSize": 12,
            "openLinksInNewTab": false,
            "markdown": "## Sample eCommerce Data\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."
          },
          "uiState": {},
          "data": {
            "aggs": [],
            "searchSource": {
              "query": {
                "query": "",
                "language": "kuery"
              },
              "filter": []
            }
          }
        },
        "enhancements": {}
      },
      "gridData": {
        "x": 0,
        "y": 0,
        "w": 24,
        "h": 15,
        "i": "1"
      },
      "type": "visualization",
      "version": "7.9.2"
    }
  ],
  "options": {
    "hidePanelTitles": false,
    "useMargins": true,
    "syncColors": false,
    "syncTooltips": true,
    "syncCursor": true
  },
  "version": 3
  },
  "references": [],
  "spaces": ["space-1"]
}'
```

</details>

## Update

<details>
<summary>Update an existing dashboard</summary>

```
curl  -X PUT \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "title": "my demo dashboard",
    "kibanaSavedObjectMeta": {
      "searchSource": {}
    },
    "timeRestore": false,
    "panels": [
      {
        "panelConfig": {
          "savedVis": {
            "description": "",
            "type": "markdown",
            "params": {
              "fontSize": 12,
              "openLinksInNewTab": false,
              "markdown": "## Sample eCommerce Data\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html).\nWubba lubba dub-dub!"
            },
            "uiState": {},
            "data": {
              "aggs": [],
              "searchSource": {
                "query": {
                  "query": "",
                  "language": "kuery"
                },
                "filter": []
              }
            }
          },
          "enhancements": {}
        },
        "gridData": {
          "x": 0,
          "y": 0,
          "w": 24,
          "h": 15
        },
        "type": "visualization"
      }
    ],
    "version": 3
  },
  "references": []
}'
```

</details>

## Get / List

<details>
<summary>Get a dashboard</summary>

```
curl  -X GET \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
    --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31'
```
</details>

<details>
<summary>Get a paginated list of dashboards</summary>

```
curl  -X GET \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31'
```
</details>

## Delete
<details>
<summary>Delete a dashboard</summary>

```
curl  -X DELETE \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true'
```

</details>

## Open API specification

<details>
<summary>Retrieve the Open API specification</summary>

```
curl  -X GET \
  'http://localhost:5601/api/oas?pathStartsWith=%2Fapi%2Fdashboard' \
  --user elastic:changeme \
  --header 'Accept: */*'
```

</details>

---------

Co-authored-by: kibanamachine <[email protected]>
(cherry picked from commit a227021)
nickpeihl added a commit to nickpeihl/kibana that referenced this issue Nov 8, 2024
Closes #[192618](elastic#192618)

Adds public CRUD+List endpoints for the Dashboards API.

The schema for the endpoints are generated from Content Management
schemas so that the RPC and Public APIs use the same schemas for CRUD
operations. A new version (v3) has been added to the Dashboards content
management specification that decouples Content from Saved Objects using
a translation layer in Content Management. When retrieving a saved
object the Content Management layer parses and validates the panelJSON,
optionsListJSON, and savedSearchJSON properties against the defines
schema and passes the translated content to the consumer (user interface
or API).

When writing a saved object, the Content Management layer serializes
(`JSON.stringify`) the Content object into the saved object schema. So
the saved object schema continues to store as stringified JSON, but the
user interface and public API see and use the JSON objects.

These planned features are out of scope for this PR and may be added in
subsequent PRs.
1) elastic#192758
2) elastic#192622

Reviewers, please test both UI and endpoints.

# cURL examples:

First, `yarn start --no-base-path`. Assumes `elastic:changeme` is the
username:password.

## Create

<details>
<summary>Create an empty dashboard with the minimum required
properties</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "attributes": { "title": "my empty dashboard" }
  }'
```

</details>

<details>
<summary>Create a dashboard of a specific ID with some ES|QL
panels</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "description": "",
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                  "name": "kibana_sample_data_ecommerce",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "title": "kibana_sample_data_ecommerce",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "title": "kibana_sample_data_ecommerce"
                    }
                  ],
                  "layers": {
                    "44866844-8fca-482a-a769-006e7d029b9b": {
                      "columns": [
                        {
                          "columnId": "6376af5c-fdd1-4d72-a3ec-5686b5049664",
                          "fieldName": "customer_gender",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        },
                        {
                          "columnId": "a2e3e039-dff6-4893-9c9d-9f0a816207dd",
                          "fieldName": "taxless_total_price",
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        }
                      ],
                      "index": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "query": {
                        "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
                      }
                    },
                    "781db49e-f4f1-42e0-975f-7118d2ef7a18": {
                      "columns": [],
                      "index": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
                      "query": {
                        "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
                      }
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_ecommerce | LIMIT 100"
              },
              "visualization": {
                "layers": [
                  {
                    "categoryDisplay": "default",
                    "colorMapping": {
                      "assignments": [],
                      "colorMode": {
                        "type": "categorical"
                      },
                      "paletteId": "eui_amsterdam_color_blind",
                      "specialAssignments": [
                        {
                          "color": {
                            "type": "loop"
                          },
                          "rule": {
                            "type": "other"
                          },
                          "touched": false
                        }
                      ]
                    },
                    "layerId": "44866844-8fca-482a-a769-006e7d029b9b",
                    "layerType": "data",
                    "legendDisplay": "default",
                    "metrics": [
                      "a2e3e039-dff6-4893-9c9d-9f0a816207dd"
                    ],
                    "nestedLegend": false,
                    "numberDisplay": "percent",
                    "primaryGroups": [
                      "6376af5c-fdd1-4d72-a3ec-5686b5049664"
                    ]
                  }
                ],
                "shape": "pie"
              }
            },
            "title": "Table category & category.keyword & currency & customer_first_name & customer_first_name.keyword",
            "type": "lens",
            "visualizationType": "lnsPie"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 0,
          "y": 0
        },
        "type": "lens"
      },
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                  "name": "kibana_sample_data_logs",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "timeFieldName": "@timestamp",
                  "title": "kibana_sample_data_logs",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                      "timeField": "@timestamp",
                      "title": "kibana_sample_data_logs"
                    }
                  ],
                  "layers": {
                    "2e3f211d-289f-4a24-87bb-1ccacd678adb": {
                      "columns": [
                        {
                          "columnId": "AVG(machine.ram)",
                          "fieldName": "AVG(machine.ram)",
                          "inMetricDimension": true,
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        },
                        {
                          "columnId": "machine.os.keyword",
                          "fieldName": "machine.os.keyword",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        }
                      ],
                      "index": "e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a",
                      "query": {
                        "esql": "FROM kibana_sample_data_logs| STATS AVG(machine.ram) BY machine.os.keyword "
                      },
                      "timeField": "@timestamp"
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_logs| STATS AVG(machine.ram) BY machine.os.keyword "
              },
              "visualization": {
                "axisTitlesVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "fittingFunction": "None",
                "gridlinesVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "labelsOrientation": {
                  "x": 0,
                  "yLeft": 0,
                  "yRight": 0
                },
                "layers": [
                  {
                    "accessors": [
                      "AVG(machine.ram)"
                    ],
                    "colorMapping": {
                      "assignments": [],
                      "colorMode": {
                        "type": "categorical"
                      },
                      "paletteId": "eui_amsterdam_color_blind",
                      "specialAssignments": [
                        {
                          "color": {
                            "type": "loop"
                          },
                          "rule": {
                            "type": "other"
                          },
                          "touched": false
                        }
                      ]
                    },
                    "layerId": "2e3f211d-289f-4a24-87bb-1ccacd678adb",
                    "layerType": "data",
                    "seriesType": "bar_stacked",
                    "xAccessor": "machine.os.keyword"
                  }
                ],
                "legend": {
                  "isVisible": true,
                  "position": "right"
                },
                "preferredSeriesType": "bar_stacked",
                "tickLabelsVisibilitySettings": {
                  "x": true,
                  "yLeft": true,
                  "yRight": true
                },
                "valueLabels": "hide"
              }
            },
            "title": "Bar vertical stacked",
            "type": "lens",
            "visualizationType": "lnsXY"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 24,
          "y": 0
        },
        "type": "lens"
      },
      {
        "panelConfig": {
          "attributes": {
            "references": [],
            "state": {
              "adHocDataViews": {
                "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03": {
                  "allowHidden": false,
                  "allowNoIndex": false,
                  "fieldFormats": {},
                  "id": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                  "name": "kibana_sample_data_flights",
                  "runtimeFieldMap": {},
                  "sourceFilters": [],
                  "title": "kibana_sample_data_flights",
                  "type": "esql"
                }
              },
              "datasourceStates": {
                "textBased": {
                  "indexPatternRefs": [
                    {
                      "id": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                      "title": "kibana_sample_data_flights"
                    }
                  ],
                  "layers": {
                    "4451c40f-b3ef-464e-b3d4-b10469f65c2a": {
                      "columns": [
                        {
                          "columnId": "AvgDelayMins",
                          "fieldName": "AvgDelayMins",
                          "inMetricDimension": true,
                          "meta": {
                            "esType": "double",
                            "type": "number"
                          }
                        },
                        {
                          "columnId": "Carrier",
                          "fieldName": "Carrier",
                          "meta": {
                            "esType": "keyword",
                            "type": "string"
                          }
                        }
                      ],
                      "index": "5d671714fc025d173ee40f0825b86d59b6e432344593b725be28f1f8f17a8a03",
                      "query": {
                        "esql": "FROM kibana_sample_data_flights| STATS AvgDelayMins = AVG(FlightDelayMin) BY Carrier "
                      }
                    }
                  }
                }
              },
              "filters": [],
              "query": {
                "esql": "FROM kibana_sample_data_flights| STATS AvgDelayMins = AVG(FlightDelayMin) BY Carrier "
              },
              "visualization": {
                "breakdownByAccessor": "Carrier",
                "layerId": "4451c40f-b3ef-464e-b3d4-b10469f65c2a",
                "layerType": "data",
                "metricAccessor": "AvgDelayMins",
                "palette": {
                  "name": "status",
                  "params": {
                    "colorStops": [],
                    "continuity": "all",
                    "maxSteps": 5,
                    "name": "status",
                    "progression": "fixed",
                    "rangeMax": 100,
                    "rangeMin": 0,
                    "rangeType": "percent",
                    "reverse": false,
                    "steps": 3,
                    "stops": [
                      {
                        "color": "#209280",
                        "stop": 33.33
                      },
                      {
                        "color": "#d6bf57",
                        "stop": 66.66
                      },
                      {
                        "color": "#cc5642",
                        "stop": 100
                      }
                    ]
                  },
                  "type": "palette"
                }
              }
            },
            "title": "Bar vertical stacked",
            "type": "lens",
            "visualizationType": "lnsMetric"
          }
        },
        "gridData": {
          "h": 15,
          "w": 24,
          "x": 0,
          "y": 15
        },
        "type": "lens"
      }
    ],
    "timeRestore": false,
    "title": "several es|ql panels",
    "version": 3
  }
}'
```
</details>

<details>
<summary>Create a dashboard with a Links panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "layout": "vertical",
            "links": [
              {
                "destinationRefName": "link_1981a00f-8120-4c80-b37f-ed38969afe09_dashboard",
                "id": "1981a00f-8120-4c80-b37f-ed38969afe09",
                "order": 0,
                "type": "dashboardLink"
              },
              {
                "destinationRefName": "link_f2e1a75c-fbca-4f41-a290-d5d89a60a797_dashboard",
                "id": "f2e1a75c-fbca-4f41-a290-d5d89a60a797",
                "order": 1,
                "type": "dashboardLink"
              },
              {
                "destination": "https://example.com",
                "id": "63342ea6-f686-42b2-a526-ec0bcf4476b0",
                "order": 2,
                "type": "externalLink"
              }
            ]
          },
          "enhancements": {},
          "id": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b"
        },
        "gridData": {
          "h": 7,
          "i": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b",
          "w": 8,
          "x": 0,
          "y": 0
        },
        "panelIndex": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b",
        "type": "links"
      }
    ],
    "timeRestore": false,
    "title": "a links panel",
    "version": 3
  },
  "references": [
    {
      "id": "722b74f0-b882-11e8-a6d9-e546fe2bba5f",
      "name": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b:link_1981a00f-8120-4c80-b37f-ed38969afe09_dashboard",
      "type": "dashboard"
    },
    {
      "id": "edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b",
      "name": "abbbaedc-62f5-46ee-9d17-8367dcf4f52b:link_f2e1a75c-fbca-4f41-a290-d5d89a60a797_dashboard",
      "type": "dashboard"
    }
  ]
}'
```
</details>

<details>
<summary>Create a dashboard with a Maps panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "description": "",
            "layerListJSON": "[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"db63eee8-3dfc-48c6-8c8b-7f2c4e32329d\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"},{\"sourceDescriptor\":{\"geoField\":\"geoip.location\",\"scalingType\":\"MVT\",\"id\":\"9ee192e4-18f0-41b2-b8b7-89eb91d0e529\",\"type\":\"ES_SEARCH\",\"applyGlobalQuery\":true,\"applyGlobalTime\":true,\"applyForceRefresh\":true,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"topHitsGroupByTimeseries\":false,\"topHitsSplitField\":\"\",\"topHitsSize\":1,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"65710bbc-f41c-4fe7-b0c3-a6dbc0613220\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"category.keyword\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":3},\"type\":\"CATEGORICAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":0}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelZoomRange\":{\"options\":{\"useLayerZoomRange\":true,\"minZoom\":0,\"maxZoom\":24}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}},\"labelPosition\":{\"options\":{\"position\":\"CENTER\"}}},\"isTimeAware\":true},\"includeInFitToBounds\":true,\"type\":\"MVT_VECTOR\",\"joins\":[],\"disableTooltips\":false}]",
            "mapStateJSON": "{\"adHocDataViews\":[],\"zoom\":1.57,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"disabled\":false,\"negate\":false,\"alias\":\"males only\",\"index\":\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\",\"key\":\"customer_gender\",\"field\":\"customer_gender\",\"params\":{\"query\":\"MALE\"},\"type\":\"phrase\"},\"query\":{\"match_phrase\":{\"customer_gender\":\"MALE\"}},\"$state\":{\"store\":\"appState\"}}],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
            "title": "",
            "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"65710bbc-f41c-4fe7-b0c3-a6dbc0613220\"]}"
          },
          "enhancements": {
            "dynamicActions": {
              "events": []
            }
          },
          "hiddenLayers": [],
          "id": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
          "isLayerTOCOpen": false,
          "mapBuffer": {
            "maxLat": 85.05113,
            "maxLon": 180,
            "minLat": -66.51326,
            "minLon": -180
          },
          "mapCenter": {
            "lat": 19.94277,
            "lon": 0,
            "zoom": 1.57
          },
          "openTOCDetails": [
            "65710bbc-f41c-4fe7-b0c3-a6dbc0613220"
          ]
        },
        "gridData": {
          "h": 25,
          "i": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
          "w": 38,
          "x": 0,
          "y": 0
        },
        "panelIndex": "108b2f72-0101-4e09-b8a9-22f7aa9573b0",
        "type": "map"
      }
    ],
    "timeRestore": false,
    "title": "a maps panel",
    "version": 3
  },
  "references": [
    {
      "type": "tag",
      "id": "662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "name": "tag-ref-662b28f2-71e4-4c04-b4e5-0c6249b1c08a"
    },
    {
      "name": "108b2f72-0101-4e09-b8a9-22f7aa9573b0:layer_1_source_index_pattern",
      "type": "index-pattern",
      "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f"
    }
  ]
}'
```

</details>

<details>
<summary>Create a dashboard with a Filter pill and a Field statistics
panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "description": "",
    "kibanaSavedObjectMeta": {
      "searchSource": {
        "filter": [
          {
            "$state": {
              "store": "appState"
            },
            "meta": {
              "alias": "gnomehouse",
              "disabled": false,
              "field": "products.manufacturer.keyword",
              "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
              "key": "products.manufacturer.keyword",
              "negate": false,
              "params": [
                "Gnomehouse",
                "Gnomehouse mom"
              ],
              "type": "phrases"
            },
            "query": {
              "bool": {
                "minimum_should_match": 1,
                "should": [
                  {
                    "match_phrase": {
                      "products.manufacturer.keyword": "Gnomehouse"
                    }
                  },
                  {
                    "match_phrase": {
                      "products.manufacturer.keyword": "Gnomehouse mom"
                    }
                  }
                ]
              }
            }
          }
        ],
        "query": {
          "language": "kuery",
          "query": ""
        }
      }
    },
    "panels": [
      {
        "panelConfig": {
          "dataViewId": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
          "enhancements": {},
          "id": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
          "query": {
            "esql": "from kibana_sample_data_ecommerce | limit 10"
          },
          "viewType": "esql"
        },
        "gridData": {
          "h": 18,
          "i": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
          "w": 48,
          "x": 0,
          "y": 0
        },
        "panelIndex": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4",
        "type": "field_stats_table"
      }
    ],
    "timeRestore": false,
    "title": "field stats panel",
    "version": 2
  },
  "references": [
    {
      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
      "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
      "type": "index-pattern"
    },
    {
      "id": "662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "name": "tag-ref-662b28f2-71e4-4c04-b4e5-0c6249b1c08a",
      "type": "tag"
    },
    {
      "id": "32eec79c9673ab1b9265f3e422e8f952778f02c82eaf13147a9c0ba86290337a",
      "name": "3c9dee70-4a01-4c2f-9ccd-0c2812e2a5d4:fieldStatsTableDataViewId",
      "type": "index-pattern"
    }
  ]
}'
```
</details>

<details>
<summary>Create a dashboard with a Lens panel</summary>

```
curl  -X POST \
  'http://localhost:5601/api/dashboards/dashboard/' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "title": "a lens panel",
    "kibanaSavedObjectMeta": {
      "searchSource": {}
    },
    "timeRestore": false,
    "panels": [
      {
        "panelConfig": {
          "attributes": {
            "title": "",
            "visualizationType": "lnsDatatable",
            "type": "lens",
            "references": [
              {
                "type": "index-pattern",
                "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d",
                "name": "indexpattern-datasource-layer-b9789655-f916-4732-9bf2-641a88075210"
              }
            ],
            "state": {
              "visualization": {
                "layerId": "b9789655-f916-4732-9bf2-641a88075210",
                "layerType": "data",
                "columns": [
                  {
                    "isTransposed": false,
                    "columnId": "4175e737-76b9-46db-894b-57106a06b9cb"
                  },
                  {
                    "isTransposed": false,
                    "columnId": "1494f183-3bfa-4602-a780-6a41624f6c69"
                  },
                  {
                    "isTransposed": false,
                    "columnId": "8eb92ea9-5b76-45a2-865e-d78511c1e506"
                  }
                ]
              },
              "query": {
                "query": "",
                "language": "kuery"
              },
              "filters": [],
              "datasourceStates": {
                "formBased": {
                  "layers": {
                    "b9789655-f916-4732-9bf2-641a88075210": {
                      "columns": {
                        "4175e737-76b9-46db-894b-57106a06b9cb": {
                          "label": "Top 5 values of Carrier",
                          "dataType": "string",
                          "operationType": "terms",
                          "scale": "ordinal",
                          "sourceField": "Carrier",
                          "isBucketed": true,
                          "params": {
                            "size": 5,
                            "orderBy": {
                              "type": "column",
                              "columnId": "1494f183-3bfa-4602-a780-6a41624f6c69"
                            },
                            "orderDirection": "desc",
                            "otherBucket": true,
                            "missingBucket": false,
                            "parentFormat": {
                              "id": "terms"
                            },
                            "include": [],
                            "exclude": [],
                            "includeIsRegex": false,
                            "excludeIsRegex": false
                          }
                        },
                        "1494f183-3bfa-4602-a780-6a41624f6c69": {
                          "label": "Count of records",
                          "dataType": "number",
                          "operationType": "count",
                          "isBucketed": false,
                          "scale": "ratio",
                          "sourceField": "___records___",
                          "params": {
                            "emptyAsNull": true
                          }
                        },
                        "8eb92ea9-5b76-45a2-865e-d78511c1e506": {
                          "label": "Median of AvgTicketPrice",
                          "dataType": "number",
                          "operationType": "median",
                          "sourceField": "AvgTicketPrice",
                          "isBucketed": false,
                          "scale": "ratio",
                          "params": {
                            "emptyAsNull": true
                          }
                        }
                      },
                      "columnOrder": [
                        "4175e737-76b9-46db-894b-57106a06b9cb",
                        "1494f183-3bfa-4602-a780-6a41624f6c69",
                        "8eb92ea9-5b76-45a2-865e-d78511c1e506"
                      ],
                      "incompleteColumns": {},
                      "sampling": 1
                    }
                  }
                },
                "indexpattern": {
                  "layers": {}
                },
                "textBased": {
                  "layers": {}
                }
              },
              "internalReferences": [],
              "adHocDataViews": {}
            }
          },
          "enhancements": {}
        },
        "gridData": {
          "x": 0,
          "y": 0,
          "w": 24,
          "h": 15
        },
        "type": "lens"
      }
    ],
    "options": {
      "hidePanelTitles": false,
      "useMargins": true,
      "syncColors": false,
      "syncTooltips": true,
      "syncCursor": true
    },
    "version": 3
  },
  "references": [
    {
      "type": "index-pattern",
      "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d",
      "name": "indexpattern-datasource-layer-b9789655-f916-4732-9bf2-641a88075210"
    }
  ]
}'
```

</details>

<details>
<summary>Create a dashboard in a specific Space</summary>

```
curl  -X POST \
  'http://localhost:5601/s/space-1/api/dashboards/dashboard/' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
  "title": "my other demo dashboard",
  "kibanaSavedObjectMeta": {
    "searchSource": {}
  },
  "timeRestore": false,
  "panels": [
    {
      "panelConfig": {
        "savedVis": {
          "description": "",
          "type": "markdown",
          "params": {
            "fontSize": 12,
            "openLinksInNewTab": false,
            "markdown": "## Sample eCommerce Data\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."
          },
          "uiState": {},
          "data": {
            "aggs": [],
            "searchSource": {
              "query": {
                "query": "",
                "language": "kuery"
              },
              "filter": []
            }
          }
        },
        "enhancements": {}
      },
      "gridData": {
        "x": 0,
        "y": 0,
        "w": 24,
        "h": 15,
        "i": "1"
      },
      "type": "visualization",
      "version": "7.9.2"
    }
  ],
  "options": {
    "hidePanelTitles": false,
    "useMargins": true,
    "syncColors": false,
    "syncTooltips": true,
    "syncCursor": true
  },
  "version": 3
  },
  "references": [],
  "spaces": ["space-1"]
}'
```

</details>

## Update

<details>
<summary>Update an existing dashboard</summary>

```
curl  -X PUT \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "attributes": {
    "title": "my demo dashboard",
    "kibanaSavedObjectMeta": {
      "searchSource": {}
    },
    "timeRestore": false,
    "panels": [
      {
        "panelConfig": {
          "savedVis": {
            "description": "",
            "type": "markdown",
            "params": {
              "fontSize": 12,
              "openLinksInNewTab": false,
              "markdown": "## Sample eCommerce Data\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html).\nWubba lubba dub-dub!"
            },
            "uiState": {},
            "data": {
              "aggs": [],
              "searchSource": {
                "query": {
                  "query": "",
                  "language": "kuery"
                },
                "filter": []
              }
            }
          },
          "enhancements": {}
        },
        "gridData": {
          "x": 0,
          "y": 0,
          "w": 24,
          "h": 15
        },
        "type": "visualization"
      }
    ],
    "version": 3
  },
  "references": []
}'
```

</details>

## Get / List

<details>
<summary>Get a dashboard</summary>

```
curl  -X GET \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
    --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31'
```
</details>

<details>
<summary>Get a paginated list of dashboards</summary>

```
curl  -X GET \
  'http://localhost:5601/api/dashboards/dashboard' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31'
```
</details>

## Delete
<details>
<summary>Delete a dashboard</summary>

```
curl  -X DELETE \
  'http://localhost:5601/api/dashboards/dashboard/foo-123' \
  --user elastic:changeme \
  --header 'Accept: */*' \
  --header 'elastic-api-version: 2023-10-31' \
  --header 'kbn-xsrf: true'
```

</details>

## Open API specification

<details>
<summary>Retrieve the Open API specification</summary>

```
curl  -X GET \
  'http://localhost:5601/api/oas?pathStartsWith=%2Fapi%2Fdashboard' \
  --user elastic:changeme \
  --header 'Accept: */*'
```

</details>

---------

Co-authored-by: kibanamachine <[email protected]>
(cherry picked from commit a227021)
nickpeihl added a commit to nickpeihl/kibana that referenced this issue Dec 13, 2024
Unfortunately, the `getSerializedState` method still needs to be async since the `getDashboardState` method on the CM service is a promise. But this might change when we move reference handling to the server in elastic#192758.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas
Projects
None yet
Development

No branches or pull requests

1 participant