mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Dashboard] Add collapsible sections (#220877)
Closes https://github.com/elastic/kibana/issues/1547 Closes https://github.com/elastic/kibana/issues/190342 Closes https://github.com/elastic/kibana/issues/197716 ## Summary This PR adds the ability for collapsible sections to be created and managed on Dashboards. https://github.com/user-attachments/assets/c5c046d0-58f1-45e1-88b3-33421f3ec002 > [!NOTE] > Most of the work for developing collapsible sections occurred in PRs contained to the `kbn-grid-layout` package (see [this meta issue](https://github.com/elastic/kibana/issues/190342) to track this work) - this PR simply makes them available on Dashboards by adding them as a widget that can be added through the "Add panel" menu. As a result of this, most work is contained in the Dashboard plugin - changes made to the `kbn-grid-layout` package only include adding IDs for additional tests that were added for the Dashboard integration. ### Technical Details #### Content Management Schema The content management schema allows for panels and sections to be mixed within the single `panels` key for a dashboard **without** worrying about section IDs; for example: ``` { "panels": [ { // this is a simplified panel "gridData": { "x": 0, "y": 0, "w": 12, "h": 8, }, "panelConfig": { ... }, }, { // this is a section "gridData": { "y": 9, }, "collapsed": false, "title": "Section title", "panels": [ { // this is a simplified panel "gridData": { "x": 0, "y": 0, "w": 24, "h": 16, }, "panelConfig": { ... }, }, ], }, ] } ``` #### Saved Object Schema The dashboard saved object schema, on the other hand, separates out sections and panels under different keys - this is because, while we are stuck with panels being stored as `panelJSON`, I didn't want to add much to this. So, under grid data for each panel, they have an optional `sectionId` which then links to a section in the `sections` array in the saved object: ``` { "panelsJSON": "<...> \"gridData\":{\"i\":\"panelId\",\"y\":0,\"x\":0,\"w\":12,\"h\":8,\"sectionId\":\"someSectionId\"} <...>" "sections": [ { "collapsed": false, "title": "Section title", "gridData": { "i": "someSectionId", "y": 8. } } ], } ``` This allows sections to be serialized **without** being stringified. This storage also matches how we store this data in runtime using `layout`. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ## Release note Adds collapsible sections to Dashboard, which allow panels to grouped into sections that will not load their contents when their assigned section is collapsed. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
b3f79c809f
commit
74ee116780
78 changed files with 5615 additions and 1982 deletions
|
@ -7469,6 +7469,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -7568,6 +7570,147 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData",
|
||||||
|
"panelIndex"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData",
|
||||||
|
"panels"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -8130,6 +8273,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -8229,6 +8374,148 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the section",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"default": [],
|
||||||
|
"description": "The panels that belong to the section.",
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"description": "The unique ID of the panel.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -8675,6 +8962,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -8774,6 +9063,147 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData",
|
||||||
|
"panelIndex"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData",
|
||||||
|
"panels"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -9308,6 +9738,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -9407,6 +9839,148 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the section",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"default": [],
|
||||||
|
"description": "The panels that belong to the section.",
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"description": "The unique ID of the panel.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -9847,6 +10421,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -9946,6 +10522,147 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData",
|
||||||
|
"panelIndex"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData",
|
||||||
|
"panels"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
|
|
@ -7469,6 +7469,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -7568,6 +7570,147 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData",
|
||||||
|
"panelIndex"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData",
|
||||||
|
"panels"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -8130,6 +8273,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -8229,6 +8374,148 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the section",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"default": [],
|
||||||
|
"description": "The panels that belong to the section.",
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"description": "The unique ID of the panel.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -8675,6 +8962,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -8774,6 +9063,147 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData",
|
||||||
|
"panelIndex"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData",
|
||||||
|
"panels"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -9308,6 +9738,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -9407,6 +9839,148 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the section",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"default": [],
|
||||||
|
"description": "The panels that belong to the section.",
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"description": "The unique identifier of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"description": "The unique ID of the panel.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
@ -9847,6 +10421,8 @@
|
||||||
"panels": {
|
"panels": {
|
||||||
"default": [],
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"gridData": {
|
"gridData": {
|
||||||
|
@ -9946,6 +10522,147 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"collapsed": {
|
||||||
|
"description": "The collapsed state of the section.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the section in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panels": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gridData": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"h": {
|
||||||
|
"default": 15,
|
||||||
|
"description": "The height of the panel in grid units",
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"default": 24,
|
||||||
|
"description": "The width of the panel in grid units",
|
||||||
|
"maximum": 48,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"description": "The x coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"description": "The y coordinate of the panel in grid units",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"i"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "The saved object id for by reference panels",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelConfig": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "The description of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enhancements": {
|
||||||
|
"additionalProperties": {},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"hidePanelTitles": {
|
||||||
|
"description": "Set to true to hide the panel title in its container.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"savedObjectId": {
|
||||||
|
"description": "The unique id of the library item to construct the embeddable.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "The version of the embeddable in the panel.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"panelIndex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"panelRefName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the panel",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The embeddable type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"deprecated": true,
|
||||||
|
"description": "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelConfig",
|
||||||
|
"type",
|
||||||
|
"gridData",
|
||||||
|
"panelIndex"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "The title of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"gridData",
|
||||||
|
"panels"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"refreshInterval": {
|
"refreshInterval": {
|
||||||
|
|
|
@ -6731,6 +6731,101 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- panelIndex
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
panels:
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -6807,6 +6902,14 @@ paths:
|
||||||
- gridData
|
- gridData
|
||||||
- panelIndex
|
- panelIndex
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
- panels
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -7201,6 +7304,103 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the panel
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
description: The unique ID of the panel.
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the section
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
panels:
|
||||||
|
default: []
|
||||||
|
description: The panels that belong to the section.
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -7277,6 +7477,13 @@ paths:
|
||||||
- type
|
- type
|
||||||
- gridData
|
- gridData
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -7594,6 +7801,101 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- panelIndex
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
panels:
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -7670,6 +7972,14 @@ paths:
|
||||||
- gridData
|
- gridData
|
||||||
- panelIndex
|
- panelIndex
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
- panels
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -8044,6 +8354,103 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the panel
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
description: The unique ID of the panel.
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the section
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
panels:
|
||||||
|
default: []
|
||||||
|
description: The panels that belong to the section.
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -8120,6 +8527,13 @@ paths:
|
||||||
- type
|
- type
|
||||||
- gridData
|
- gridData
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -8433,6 +8847,101 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- panelIndex
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
panels:
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -8509,6 +9018,14 @@ paths:
|
||||||
- gridData
|
- gridData
|
||||||
- panelIndex
|
- panelIndex
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
- panels
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
|
|
@ -8273,6 +8273,101 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- panelIndex
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
panels:
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -8349,6 +8444,14 @@ paths:
|
||||||
- gridData
|
- gridData
|
||||||
- panelIndex
|
- panelIndex
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
- panels
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -8743,6 +8846,103 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the panel
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
description: The unique ID of the panel.
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the section
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
panels:
|
||||||
|
default: []
|
||||||
|
description: The panels that belong to the section.
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -8819,6 +9019,13 @@ paths:
|
||||||
- type
|
- type
|
||||||
- gridData
|
- gridData
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -9136,6 +9343,101 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- panelIndex
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
panels:
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -9212,6 +9514,14 @@ paths:
|
||||||
- gridData
|
- gridData
|
||||||
- panelIndex
|
- panelIndex
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
- panels
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -9586,6 +9896,103 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the panel
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
description: The unique ID of the panel.
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
description: The unique identifier of the section
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
panels:
|
||||||
|
default: []
|
||||||
|
description: The panels that belong to the section.
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -9662,6 +10069,13 @@ paths:
|
||||||
- type
|
- type
|
||||||
- gridData
|
- gridData
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
@ -9975,6 +10389,101 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
panels:
|
panels:
|
||||||
default: []
|
default: []
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
h:
|
||||||
|
default: 15
|
||||||
|
description: The height of the panel in grid units
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
w:
|
||||||
|
default: 24
|
||||||
|
description: The width of the panel in grid units
|
||||||
|
maximum: 48
|
||||||
|
minimum: 1
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
description: The x coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the panel in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
id:
|
||||||
|
description: The saved object id for by reference panels
|
||||||
|
type: string
|
||||||
|
panelConfig:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: The description of the panel
|
||||||
|
type: string
|
||||||
|
enhancements:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
hidePanelTitles:
|
||||||
|
description: Set to true to hide the panel title in its container.
|
||||||
|
type: boolean
|
||||||
|
savedObjectId:
|
||||||
|
description: The unique id of the library item to construct the embeddable.
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
description: The version of the embeddable in the panel.
|
||||||
|
type: string
|
||||||
|
panelIndex:
|
||||||
|
type: string
|
||||||
|
panelRefName:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
description: The title of the panel
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The embeddable type
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
deprecated: true
|
||||||
|
description: The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- panelConfig
|
||||||
|
- type
|
||||||
|
- gridData
|
||||||
|
- panelIndex
|
||||||
|
- additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
collapsed:
|
||||||
|
description: The collapsed state of the section.
|
||||||
|
type: boolean
|
||||||
|
gridData:
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
i:
|
||||||
|
type: string
|
||||||
|
'y':
|
||||||
|
description: The y coordinate of the section in grid units
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- 'y'
|
||||||
|
- i
|
||||||
|
panels:
|
||||||
items:
|
items:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
type: object
|
type: object
|
||||||
|
@ -10051,6 +10560,14 @@ paths:
|
||||||
- gridData
|
- gridData
|
||||||
- panelIndex
|
- panelIndex
|
||||||
type: array
|
type: array
|
||||||
|
title:
|
||||||
|
description: The title of the section.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- gridData
|
||||||
|
- panels
|
||||||
|
type: array
|
||||||
refreshInterval:
|
refreshInterval:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: A container for various refresh interval settings
|
description: A container for various refresh interval settings
|
||||||
|
|
|
@ -285,6 +285,7 @@
|
||||||
"refreshInterval.pause",
|
"refreshInterval.pause",
|
||||||
"refreshInterval.section",
|
"refreshInterval.section",
|
||||||
"refreshInterval.value",
|
"refreshInterval.value",
|
||||||
|
"sections",
|
||||||
"timeFrom",
|
"timeFrom",
|
||||||
"timeRestore",
|
"timeRestore",
|
||||||
"timeTo",
|
"timeTo",
|
||||||
|
|
|
@ -982,6 +982,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sections": {
|
||||||
|
"dynamic": false,
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
"timeFrom": {
|
"timeFrom": {
|
||||||
"doc_values": false,
|
"doc_values": false,
|
||||||
"index": false,
|
"index": false,
|
||||||
|
|
|
@ -90,7 +90,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
||||||
"connector_token": "79977ea2cb1530ba7e315b95c1b5a524b622a6b3",
|
"connector_token": "79977ea2cb1530ba7e315b95c1b5a524b622a6b3",
|
||||||
"core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff",
|
"core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff",
|
||||||
"csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654",
|
"csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654",
|
||||||
"dashboard": "211e9ca30f5a95d5f3c27b1bf2b58e6cfa0c9ae9",
|
"dashboard": "7fea2b6f8f860ac4f665fd0d5c91645ac248fd56",
|
||||||
"dynamic-config-overrides": "eb3ec7d96a42991068eda5421eecba9349c82d2b",
|
"dynamic-config-overrides": "eb3ec7d96a42991068eda5421eecba9349c82d2b",
|
||||||
"endpoint:unified-user-artifact-manifest": "71c7fcb52c658b21ea2800a6b6a76972ae1c776e",
|
"endpoint:unified-user-artifact-manifest": "71c7fcb52c658b21ea2800a6b6a76972ae1c776e",
|
||||||
"endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b",
|
"endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b",
|
||||||
|
|
|
@ -38,6 +38,11 @@ const renderGridLayout = (propsOverrides: Partial<GridLayoutProps> = {}) => {
|
||||||
|
|
||||||
const { rerender, ...rtlRest } = render(<GridLayout {...props} />, { wrapper: EuiThemeProvider });
|
const { rerender, ...rtlRest } = render(<GridLayout {...props} />, { wrapper: EuiThemeProvider });
|
||||||
|
|
||||||
|
const gridLayout = screen.getByTestId('kbnGridLayout');
|
||||||
|
jest.spyOn(gridLayout, 'getBoundingClientRect').mockImplementation(() => {
|
||||||
|
return { top: 0, bottom: 500 } as DOMRect;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...rtlRest,
|
...rtlRest,
|
||||||
rerender: (overrides: Partial<GridLayoutProps>) => {
|
rerender: (overrides: Partial<GridLayoutProps>) => {
|
||||||
|
|
|
@ -198,6 +198,7 @@ export const GridLayout = ({
|
||||||
<GridLayoutContext.Provider value={memoizedContext}>
|
<GridLayoutContext.Provider value={memoizedContext}>
|
||||||
<GridHeightSmoother>
|
<GridHeightSmoother>
|
||||||
<div
|
<div
|
||||||
|
data-test-subj="kbnGridLayout"
|
||||||
ref={(divElement) => {
|
ref={(divElement) => {
|
||||||
layoutRef.current = divElement;
|
layoutRef.current = divElement;
|
||||||
setDimensionsRef(divElement);
|
setDimensionsRef(divElement);
|
||||||
|
|
|
@ -35,6 +35,7 @@ export const DeleteGridSectionModal = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiModal
|
<EuiModal
|
||||||
|
data-test-subj={`kbnGridLayoutDeleteSectionModal-${sectionId}`}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setDeleteModalVisible(false);
|
setDeleteModalVisible(false);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -174,6 +174,10 @@ export const GridSectionHeader = React.memo(({ sectionId }: GridSectionHeaderPro
|
||||||
|
|
||||||
section.isCollapsed = !section.isCollapsed;
|
section.isCollapsed = !section.isCollapsed;
|
||||||
gridLayoutStateManager.gridLayout$.next(newLayout);
|
gridLayoutStateManager.gridLayout$.next(newLayout);
|
||||||
|
|
||||||
|
const buttonRef = collapseButtonRef.current;
|
||||||
|
if (!buttonRef) return;
|
||||||
|
buttonRef.setAttribute('aria-expanded', `${!section.isCollapsed}`);
|
||||||
}, [gridLayoutStateManager, sectionId]);
|
}, [gridLayoutStateManager, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -110,6 +110,7 @@ export const GridSectionTitle = React.memo(
|
||||||
size="m"
|
size="m"
|
||||||
id={`kbnGridSectionTitle-${sectionId}`}
|
id={`kbnGridSectionTitle-${sectionId}`}
|
||||||
aria-controls={`kbnGridSection-${sectionId}`}
|
aria-controls={`kbnGridSection-${sectionId}`}
|
||||||
|
aria-expanded={!currentSection?.isCollapsed}
|
||||||
data-test-subj={`kbnGridSectionTitle-${sectionId}`}
|
data-test-subj={`kbnGridSectionTitle-${sectionId}`}
|
||||||
textProps={false}
|
textProps={false}
|
||||||
className={'kbnGridSectionTitle--button'}
|
className={'kbnGridSectionTitle--button'}
|
||||||
|
|
|
@ -86,6 +86,7 @@ export const GridSectionWrapper = React.memo(({ sectionId }: GridSectionProps) =
|
||||||
gridLayoutStateManager.sectionRefs.current[sectionId] = rowRef;
|
gridLayoutStateManager.sectionRefs.current[sectionId] = rowRef;
|
||||||
}}
|
}}
|
||||||
className={'kbnGridSection'}
|
className={'kbnGridSection'}
|
||||||
|
data-test-subj={`kbnGridSectionWrapper-${sectionId}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,9 +88,17 @@ export const moveAction = (
|
||||||
let previousSection;
|
let previousSection;
|
||||||
let targetSectionId: string | undefined = (() => {
|
let targetSectionId: string | undefined = (() => {
|
||||||
if (isResize) return lastSectionId;
|
if (isResize) return lastSectionId;
|
||||||
// early return - target the first "main" section if the panel is dragged above the layout element
|
const layoutRect = gridLayoutElement?.getBoundingClientRect();
|
||||||
if (previewRect.top < (gridLayoutElement?.getBoundingClientRect().top ?? 0)) {
|
// early returns for edge cases
|
||||||
|
if (previewRect.top < (layoutRect?.top ?? 0)) {
|
||||||
|
// target the first "main" section if the panel is dragged above the layout element
|
||||||
return `main-0`;
|
return `main-0`;
|
||||||
|
} else if (previewRect.top > (layoutRect?.bottom ?? Infinity)) {
|
||||||
|
// target the last "main" section if the panel is dragged below the layout element
|
||||||
|
const sections = Object.values(currentLayout);
|
||||||
|
const maxOrder = sections.length - 1;
|
||||||
|
previousSection = sections.filter(({ order }) => order === maxOrder)[0].id;
|
||||||
|
return `main-${maxOrder}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const previewBottom = previewRect.top + rowHeight;
|
const previewBottom = previewRect.top + rowHeight;
|
||||||
|
|
|
@ -62,6 +62,7 @@ export const isLayoutEqual = (a: GridLayoutData, b: GridLayoutData) => {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const widgetA = a[key];
|
const widgetA = a[key];
|
||||||
const widgetB = b[key];
|
const widgetB = b[key];
|
||||||
|
if (!widgetA || !widgetB) return widgetA === widgetB;
|
||||||
|
|
||||||
if (widgetA.type === 'panel' && widgetB.type === 'panel') {
|
if (widgetA.type === 'panel' && widgetB.type === 'panel') {
|
||||||
isEqual = isGridDataEqual(widgetA, widgetB);
|
isEqual = isGridDataEqual(widgetA, widgetB);
|
||||||
|
|
|
@ -17,6 +17,7 @@ export {
|
||||||
type CanDuplicatePanels,
|
type CanDuplicatePanels,
|
||||||
type CanExpandPanels,
|
type CanExpandPanels,
|
||||||
} from './interfaces/panel_management';
|
} from './interfaces/panel_management';
|
||||||
|
export { type CanAddNewSection, apiCanAddNewSection } from './interfaces/can_add_new_section';
|
||||||
export {
|
export {
|
||||||
canTrackContentfulRender,
|
canTrackContentfulRender,
|
||||||
type TrackContentfulRender,
|
type TrackContentfulRender,
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CanAddNewSection {
|
||||||
|
addNewSection: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const apiCanAddNewSection = (api: unknown): api is CanAddNewSection => {
|
||||||
|
return typeof (api as CanAddNewSection)?.addNewSection === 'function';
|
||||||
|
};
|
|
@ -7,15 +7,14 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { combineLatest, debounceTime, distinctUntilChanged, map, of, switchMap } from 'rxjs';
|
|
||||||
import deepEqual from 'fast-deep-equal';
|
|
||||||
import {
|
import {
|
||||||
apiHasUniqueId,
|
|
||||||
apiPublishesUnsavedChanges,
|
|
||||||
HasUniqueId,
|
HasUniqueId,
|
||||||
PublishesUnsavedChanges,
|
PublishesUnsavedChanges,
|
||||||
PublishingSubject,
|
PublishingSubject,
|
||||||
|
apiHasUniqueId,
|
||||||
|
apiPublishesUnsavedChanges,
|
||||||
} from '@kbn/presentation-publishing';
|
} from '@kbn/presentation-publishing';
|
||||||
|
import { combineLatest, debounceTime, map, of, switchMap } from 'rxjs';
|
||||||
|
|
||||||
export const DEBOUNCE_TIME = 100;
|
export const DEBOUNCE_TIME = 100;
|
||||||
|
|
||||||
|
@ -27,8 +26,6 @@ export function childrenUnsavedChanges$<Api extends unknown = unknown>(
|
||||||
) {
|
) {
|
||||||
return children$.pipe(
|
return children$.pipe(
|
||||||
map((children) => Object.keys(children)),
|
map((children) => Object.keys(children)),
|
||||||
distinctUntilChanged(deepEqual),
|
|
||||||
|
|
||||||
// children may change, so make sure we subscribe/unsubscribe with switchMap
|
// children may change, so make sure we subscribe/unsubscribe with switchMap
|
||||||
switchMap((newChildIds: string[]) => {
|
switchMap((newChildIds: string[]) => {
|
||||||
if (newChildIds.length === 0) return of([]);
|
if (newChildIds.length === 0) return of([]);
|
||||||
|
|
|
@ -7,5 +7,11 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type { GridData, DashboardItem, SavedDashboardPanel } from '../v1/types'; // no changes made to types from v1 to v2
|
export type { DashboardItem } from '../v1/types'; // no changes made to types from v1 to v2
|
||||||
export type { ControlGroupAttributes, DashboardCrudTypes, DashboardAttributes } from './types';
|
export type {
|
||||||
|
ControlGroupAttributes,
|
||||||
|
DashboardCrudTypes,
|
||||||
|
DashboardAttributes,
|
||||||
|
GridData,
|
||||||
|
SavedDashboardPanel,
|
||||||
|
} from './types';
|
||||||
|
|
|
@ -16,7 +16,18 @@ import { DashboardContentType } from '../types';
|
||||||
import {
|
import {
|
||||||
ControlGroupAttributesV1,
|
ControlGroupAttributesV1,
|
||||||
DashboardAttributes as DashboardAttributesV1,
|
DashboardAttributes as DashboardAttributesV1,
|
||||||
|
GridData as GridDataV1,
|
||||||
|
SavedDashboardPanel as SavedDashboardPanelV1,
|
||||||
} from '../v1/types';
|
} from '../v1/types';
|
||||||
|
import { DashboardSectionState } from '../..';
|
||||||
|
|
||||||
|
export type GridData = GridDataV1 & {
|
||||||
|
sectionId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SavedDashboardPanel = Omit<SavedDashboardPanelV1, 'gridData'> & {
|
||||||
|
gridData: GridData;
|
||||||
|
};
|
||||||
|
|
||||||
export type ControlGroupAttributes = ControlGroupAttributesV1 & {
|
export type ControlGroupAttributes = ControlGroupAttributesV1 & {
|
||||||
showApplySelections?: boolean;
|
showApplySelections?: boolean;
|
||||||
|
@ -24,6 +35,7 @@ export type ControlGroupAttributes = ControlGroupAttributesV1 & {
|
||||||
|
|
||||||
export type DashboardAttributes = Omit<DashboardAttributesV1, 'controlGroupInput'> & {
|
export type DashboardAttributes = Omit<DashboardAttributesV1, 'controlGroupInput'> & {
|
||||||
controlGroupInput?: ControlGroupAttributes;
|
controlGroupInput?: ControlGroupAttributes;
|
||||||
|
sections?: DashboardSectionState[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DashboardCrudTypes = ContentManagementCrudTypes<
|
export type DashboardCrudTypes = ContentManagementCrudTypes<
|
||||||
|
|
|
@ -26,6 +26,7 @@ const dashboardWithExtractedPanel: ParsedDashboardAttributesWithType = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sections: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractedSavedObjectPanelRef = {
|
const extractedSavedObjectPanelRef = {
|
||||||
|
@ -47,6 +48,7 @@ const unextractedDashboardState: ParsedDashboardAttributesWithType = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sections: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('inject/extract by reference panel', () => {
|
describe('inject/extract by reference panel', () => {
|
||||||
|
@ -85,6 +87,7 @@ const dashboardWithExtractedByValuePanel: ParsedDashboardAttributesWithType = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sections: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractedByValueRef = {
|
const extractedByValueRef = {
|
||||||
|
@ -106,6 +109,7 @@ const unextractedDashboardByValueState: ParsedDashboardAttributesWithType = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sections: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('inject/extract by value panels', () => {
|
describe('inject/extract by value panels', () => {
|
||||||
|
|
|
@ -11,6 +11,17 @@ import type { Reference } from '@kbn/content-management-utils';
|
||||||
|
|
||||||
import type { GridData } from '../../server/content_management';
|
import type { GridData } from '../../server/content_management';
|
||||||
|
|
||||||
|
export interface DashboardSectionMap {
|
||||||
|
[id: string]: DashboardSectionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardSectionState {
|
||||||
|
title: string;
|
||||||
|
collapsed?: boolean; // if undefined, then collapsed is false
|
||||||
|
readonly gridData: Pick<GridData, 'i' | 'y'>;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DashboardPanelMap {
|
export interface DashboardPanelMap {
|
||||||
[key: string]: DashboardPanelState;
|
[key: string]: DashboardPanelState;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +29,7 @@ export interface DashboardPanelMap {
|
||||||
export interface DashboardPanelState<PanelState = object> {
|
export interface DashboardPanelState<PanelState = object> {
|
||||||
type: string;
|
type: string;
|
||||||
explicitInput: PanelState;
|
explicitInput: PanelState;
|
||||||
readonly gridData: GridData;
|
readonly gridData: GridData & { sectionId?: string };
|
||||||
panelRefName?: string;
|
panelRefName?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,8 +11,8 @@ import type { Reference } from '@kbn/content-management-utils';
|
||||||
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types';
|
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
convertPanelMapToPanelsArray,
|
convertPanelSectionMapsToPanelsArray,
|
||||||
convertPanelsArrayToPanelMap,
|
convertPanelsArrayToPanelSectionMaps,
|
||||||
} from '../../lib/dashboard_panel_converters';
|
} from '../../lib/dashboard_panel_converters';
|
||||||
import { DashboardAttributesAndReferences, ParsedDashboardAttributesWithType } from '../../types';
|
import { DashboardAttributesAndReferences, ParsedDashboardAttributesWithType } from '../../types';
|
||||||
import type { DashboardAttributes } from '../../../server/content_management';
|
import type { DashboardAttributes } from '../../../server/content_management';
|
||||||
|
@ -28,9 +28,11 @@ export interface InjectExtractDeps {
|
||||||
function parseDashboardAttributesWithType({
|
function parseDashboardAttributesWithType({
|
||||||
panels,
|
panels,
|
||||||
}: DashboardAttributes): ParsedDashboardAttributesWithType {
|
}: DashboardAttributes): ParsedDashboardAttributesWithType {
|
||||||
|
const { panels: panelsMap, sections } = convertPanelsArrayToPanelSectionMaps(panels); // drop sections
|
||||||
return {
|
return {
|
||||||
type: 'dashboard',
|
type: 'dashboard',
|
||||||
panels: convertPanelsArrayToPanelMap(panels),
|
panels: panelsMap,
|
||||||
|
sections,
|
||||||
} as ParsedDashboardAttributesWithType;
|
} as ParsedDashboardAttributesWithType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +45,10 @@ export function injectReferences(
|
||||||
// inject references back into panels via the Embeddable persistable state service.
|
// inject references back into panels via the Embeddable persistable state service.
|
||||||
const inject = createInject(deps.embeddablePersistableStateService);
|
const inject = createInject(deps.embeddablePersistableStateService);
|
||||||
const injectedState = inject(parsedAttributes, references) as ParsedDashboardAttributesWithType;
|
const injectedState = inject(parsedAttributes, references) as ParsedDashboardAttributesWithType;
|
||||||
const injectedPanels = convertPanelMapToPanelsArray(injectedState.panels);
|
const injectedPanels = convertPanelSectionMapsToPanelsArray(
|
||||||
|
injectedState.panels,
|
||||||
|
parsedAttributes.sections
|
||||||
|
); // sections don't have references
|
||||||
|
|
||||||
const newAttributes = {
|
const newAttributes = {
|
||||||
...attributes,
|
...attributes,
|
||||||
|
@ -58,7 +63,6 @@ export function extractReferences(
|
||||||
deps: InjectExtractDeps
|
deps: InjectExtractDeps
|
||||||
): DashboardAttributesAndReferences {
|
): DashboardAttributesAndReferences {
|
||||||
const parsedAttributes = parseDashboardAttributesWithType(attributes);
|
const parsedAttributes = parseDashboardAttributesWithType(attributes);
|
||||||
|
|
||||||
const panels = parsedAttributes.panels;
|
const panels = parsedAttributes.panels;
|
||||||
|
|
||||||
const panelMissingType = Object.entries(panels).find(
|
const panelMissingType = Object.entries(panels).find(
|
||||||
|
@ -73,8 +77,10 @@ export function extractReferences(
|
||||||
references: Reference[];
|
references: Reference[];
|
||||||
state: ParsedDashboardAttributesWithType;
|
state: ParsedDashboardAttributesWithType;
|
||||||
};
|
};
|
||||||
const extractedPanels = convertPanelMapToPanelsArray(extractedState.panels);
|
const extractedPanels = convertPanelSectionMapsToPanelsArray(
|
||||||
|
extractedState.panels,
|
||||||
|
parsedAttributes.sections
|
||||||
|
); // sections don't have references
|
||||||
const newAttributes = {
|
const newAttributes = {
|
||||||
...attributes,
|
...attributes,
|
||||||
panels: extractedPanels,
|
panels: extractedPanels,
|
||||||
|
|
|
@ -14,6 +14,12 @@ export type {
|
||||||
DashboardState,
|
DashboardState,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export type { DashboardPanelMap, DashboardPanelState } from './dashboard_container/types';
|
export type {
|
||||||
|
DashboardPanelMap,
|
||||||
|
DashboardPanelState,
|
||||||
|
DashboardSectionMap,
|
||||||
|
DashboardSectionState,
|
||||||
|
} from './dashboard_container/types';
|
||||||
|
|
||||||
export { type InjectExtractDeps } from './dashboard_saved_object/persistable_state/dashboard_saved_object_references';
|
export { type InjectExtractDeps } from './dashboard_saved_object/persistable_state/dashboard_saved_object_references';
|
||||||
|
export { isDashboardSection } from './lib/dashboard_panel_converters';
|
||||||
|
|
|
@ -7,22 +7,69 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import type { Reference } from '@kbn/content-management-utils';
|
import type { Reference } from '@kbn/content-management-utils';
|
||||||
import type { DashboardPanelMap } from '..';
|
import type { DashboardPanelMap, DashboardSectionMap } from '..';
|
||||||
import type { DashboardPanel } from '../../server/content_management';
|
import type {
|
||||||
|
DashboardAttributes,
|
||||||
|
DashboardPanel,
|
||||||
|
DashboardSection,
|
||||||
|
} from '../../server/content_management';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getReferencesForPanelId,
|
getReferencesForPanelId,
|
||||||
prefixReferencesFromPanel,
|
prefixReferencesFromPanel,
|
||||||
} from '../dashboard_container/persistable_state/dashboard_container_references';
|
} from '../dashboard_container/persistable_state/dashboard_container_references';
|
||||||
|
|
||||||
export const convertPanelsArrayToPanelMap = (panels?: DashboardPanel[]): DashboardPanelMap => {
|
export const isDashboardSection = (
|
||||||
|
widget: DashboardAttributes['panels'][number]
|
||||||
|
): widget is DashboardSection => {
|
||||||
|
return 'panels' in widget;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertPanelsArrayToPanelSectionMaps = (
|
||||||
|
panels?: DashboardAttributes['panels']
|
||||||
|
): { panels: DashboardPanelMap; sections: DashboardSectionMap } => {
|
||||||
const panelsMap: DashboardPanelMap = {};
|
const panelsMap: DashboardPanelMap = {};
|
||||||
panels?.forEach((panel, idx) => {
|
const sectionsMap: DashboardSectionMap = {};
|
||||||
panelsMap![panel.panelIndex ?? String(idx)] = {
|
|
||||||
|
/**
|
||||||
|
* panels and sections are mixed in the DashboardAttributes 'panels' key, so we need
|
||||||
|
* to separate them out into separate maps for the dashboard client side code
|
||||||
|
*/
|
||||||
|
panels?.forEach((widget, i) => {
|
||||||
|
if (isDashboardSection(widget)) {
|
||||||
|
const sectionId = widget.gridData.i ?? String(i);
|
||||||
|
const { panels: sectionPanels, ...restOfSection } = widget;
|
||||||
|
sectionsMap[sectionId] = {
|
||||||
|
...restOfSection,
|
||||||
|
gridData: {
|
||||||
|
...widget.gridData,
|
||||||
|
i: sectionId,
|
||||||
|
},
|
||||||
|
id: sectionId,
|
||||||
|
};
|
||||||
|
(sectionPanels as DashboardPanel[]).forEach((panel, j) => {
|
||||||
|
const panelId = panel.panelIndex ?? String(j);
|
||||||
|
const transformed = transformPanel(panel);
|
||||||
|
panelsMap[panelId] = {
|
||||||
|
...transformed,
|
||||||
|
gridData: { ...transformed.gridData, sectionId, i: panelId },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if not a section, then this widget is a panel
|
||||||
|
panelsMap[widget.panelIndex ?? String(i)] = transformPanel(widget);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { panels: panelsMap, sections: sectionsMap };
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformPanel = (panel: DashboardPanel): DashboardPanelMap[string] => {
|
||||||
|
return {
|
||||||
type: panel.type,
|
type: panel.type,
|
||||||
gridData: panel.gridData,
|
gridData: panel.gridData,
|
||||||
panelRefName: panel.panelRefName,
|
panelRefName: panel.panelRefName,
|
||||||
|
@ -33,18 +80,24 @@ export const convertPanelsArrayToPanelMap = (panels?: DashboardPanel[]): Dashboa
|
||||||
},
|
},
|
||||||
version: panel.version,
|
version: panel.version,
|
||||||
};
|
};
|
||||||
});
|
|
||||||
return panelsMap;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const convertPanelMapToPanelsArray = (
|
export const convertPanelSectionMapsToPanelsArray = (
|
||||||
panels: DashboardPanelMap,
|
panels: DashboardPanelMap,
|
||||||
|
sections: DashboardSectionMap,
|
||||||
removeLegacyVersion?: boolean
|
removeLegacyVersion?: boolean
|
||||||
) => {
|
): DashboardAttributes['panels'] => {
|
||||||
return Object.entries(panels).map(([panelId, panelState]) => {
|
const combined: DashboardAttributes['panels'] = [];
|
||||||
|
|
||||||
|
const panelsInSections: { [sectionId: string]: DashboardSection } = {};
|
||||||
|
Object.entries(sections).forEach(([sectionId, sectionState]) => {
|
||||||
|
panelsInSections[sectionId] = { ...omit(sectionState, 'id'), panels: [] };
|
||||||
|
});
|
||||||
|
Object.entries(panels).forEach(([panelId, panelState]) => {
|
||||||
const savedObjectId = (panelState.explicitInput as { savedObjectId?: string }).savedObjectId;
|
const savedObjectId = (panelState.explicitInput as { savedObjectId?: string }).savedObjectId;
|
||||||
const title = (panelState.explicitInput as { title?: string }).title;
|
const title = (panelState.explicitInput as { title?: string }).title;
|
||||||
return {
|
const { sectionId, ...gridData } = panelState.gridData; // drop section ID
|
||||||
|
const convertedPanelState = {
|
||||||
/**
|
/**
|
||||||
* Version information used to be stored in the panel until 8.11 when it was moved to live inside the
|
* Version information used to be stored in the panel until 8.11 when it was moved to live inside the
|
||||||
* explicit Embeddable Input. If removeLegacyVersion is not passed, we'd like to keep this information for
|
* explicit Embeddable Input. If removeLegacyVersion is not passed, we'd like to keep this information for
|
||||||
|
@ -53,14 +106,22 @@ export const convertPanelMapToPanelsArray = (
|
||||||
...(!removeLegacyVersion ? { version: panelState.version } : {}),
|
...(!removeLegacyVersion ? { version: panelState.version } : {}),
|
||||||
|
|
||||||
type: panelState.type,
|
type: panelState.type,
|
||||||
gridData: panelState.gridData,
|
gridData,
|
||||||
panelIndex: panelId,
|
panelIndex: panelId,
|
||||||
panelConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']),
|
panelConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']),
|
||||||
...(title !== undefined && { title }),
|
...(title !== undefined && { title }),
|
||||||
...(savedObjectId !== undefined && { id: savedObjectId }),
|
...(savedObjectId !== undefined && { id: savedObjectId }),
|
||||||
...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }),
|
...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (sectionId) {
|
||||||
|
panelsInSections[sectionId].panels.push(convertedPanelState);
|
||||||
|
} else {
|
||||||
|
combined.push(convertedPanelState);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return [...combined, ...Object.values(panelsInSections)];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { ScopedHistory } from '@kbn/core-application-browser';
|
||||||
|
|
||||||
import { ForwardedDashboardState } from './locator';
|
import { ForwardedDashboardState } from './locator';
|
||||||
import type { DashboardState } from '../types';
|
import type { DashboardState } from '../types';
|
||||||
import { convertPanelsArrayToPanelMap } from '../lib/dashboard_panel_converters';
|
import { convertPanelsArrayToPanelSectionMaps } from '../lib/dashboard_panel_converters';
|
||||||
|
|
||||||
export const loadDashboardHistoryLocationState = (
|
export const loadDashboardHistoryLocationState = (
|
||||||
getScopedHistory: () => ScopedHistory
|
getScopedHistory: () => ScopedHistory
|
||||||
|
@ -29,6 +29,6 @@ export const loadDashboardHistoryLocationState = (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...restOfState,
|
...restOfState,
|
||||||
...{ panels: convertPanelsArrayToPanelMap(panels) },
|
...convertPanelsArrayToPanelSectionMaps(panels),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,11 +17,12 @@ import type {
|
||||||
ControlGroupSerializedState,
|
ControlGroupSerializedState,
|
||||||
} from '@kbn/controls-plugin/common';
|
} from '@kbn/controls-plugin/common';
|
||||||
|
|
||||||
import type { DashboardPanelMap } from './dashboard_container/types';
|
import type { DashboardPanelMap, DashboardSectionMap } from './dashboard_container/types';
|
||||||
import type {
|
import type {
|
||||||
DashboardAttributes,
|
DashboardAttributes,
|
||||||
DashboardOptions,
|
DashboardOptions,
|
||||||
DashboardPanel,
|
DashboardPanel,
|
||||||
|
DashboardSection,
|
||||||
} from '../server/content_management';
|
} from '../server/content_management';
|
||||||
|
|
||||||
export interface DashboardCapabilities {
|
export interface DashboardCapabilities {
|
||||||
|
@ -37,6 +38,7 @@ export interface DashboardCapabilities {
|
||||||
export interface ParsedDashboardAttributesWithType {
|
export interface ParsedDashboardAttributesWithType {
|
||||||
id: string;
|
id: string;
|
||||||
panels: DashboardPanelMap;
|
panels: DashboardPanelMap;
|
||||||
|
sections: DashboardSectionMap;
|
||||||
type: 'dashboard';
|
type: 'dashboard';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +61,7 @@ export interface DashboardState extends DashboardSettings {
|
||||||
refreshInterval?: RefreshInterval;
|
refreshInterval?: RefreshInterval;
|
||||||
viewMode: ViewMode;
|
viewMode: ViewMode;
|
||||||
panels: DashboardPanelMap;
|
panels: DashboardPanelMap;
|
||||||
|
sections: DashboardSectionMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary. Currently Dashboards are in charge of providing references to all of their children.
|
* Temporary. Currently Dashboards are in charge of providing references to all of their children.
|
||||||
|
@ -78,7 +81,7 @@ export interface DashboardState extends DashboardSettings {
|
||||||
* Do not change type without considering BWC of stored URLs
|
* Do not change type without considering BWC of stored URLs
|
||||||
*/
|
*/
|
||||||
export type SharedDashboardState = Partial<
|
export type SharedDashboardState = Partial<
|
||||||
Omit<DashboardState, 'panels'> & {
|
Omit<DashboardState, 'panels' | 'sections'> & {
|
||||||
controlGroupInput?: DashboardState['controlGroupInput'] & SerializableRecord;
|
controlGroupInput?: DashboardState['controlGroupInput'] & SerializableRecord;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,7 +90,7 @@ export type SharedDashboardState = Partial<
|
||||||
*/
|
*/
|
||||||
controlGroupState?: Partial<ControlGroupRuntimeState> & SerializableRecord;
|
controlGroupState?: Partial<ControlGroupRuntimeState> & SerializableRecord;
|
||||||
|
|
||||||
panels: DashboardPanel[];
|
panels: Array<DashboardPanel | DashboardSection>;
|
||||||
|
|
||||||
references?: DashboardState['references'] & SerializableRecord;
|
references?: DashboardState['references'] & SerializableRecord;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { ADD_PANEL_ANNOTATION_GROUP } from '@kbn/embeddable-plugin/public';
|
||||||
|
import { apiCanAddNewSection, CanAddNewSection } from '@kbn/presentation-containers';
|
||||||
|
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||||
|
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||||
|
|
||||||
|
import { ACTION_ADD_SECTION } from './constants';
|
||||||
|
|
||||||
|
type AddSectionActionApi = CanAddNewSection;
|
||||||
|
|
||||||
|
const isApiCompatible = (api: unknown | null): api is AddSectionActionApi =>
|
||||||
|
Boolean(apiCanAddNewSection(api));
|
||||||
|
|
||||||
|
export class AddSectionAction implements Action<EmbeddableApiContext> {
|
||||||
|
public readonly type = ACTION_ADD_SECTION;
|
||||||
|
public readonly id = ACTION_ADD_SECTION;
|
||||||
|
public order = 40;
|
||||||
|
public grouping = [ADD_PANEL_ANNOTATION_GROUP];
|
||||||
|
|
||||||
|
public getDisplayName() {
|
||||||
|
return i18n.translate('dashboard.collapsibleSection.displayName', {
|
||||||
|
defaultMessage: 'Collapsible section',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIconType() {
|
||||||
|
return 'section';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||||
|
return isApiCompatible(embeddable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||||
|
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||||
|
embeddable.addNewSection();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,4 +15,5 @@ export const ACTION_COPY_TO_DASHBOARD = 'copyToDashboard';
|
||||||
export const ACTION_EXPAND_PANEL = 'togglePanel';
|
export const ACTION_EXPAND_PANEL = 'togglePanel';
|
||||||
export const ACTION_EXPORT_CSV = 'ACTION_EXPORT_CSV';
|
export const ACTION_EXPORT_CSV = 'ACTION_EXPORT_CSV';
|
||||||
export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary';
|
export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary';
|
||||||
|
export const ACTION_ADD_SECTION = 'addCollapsibleSection';
|
||||||
export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION';
|
export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION';
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public';
|
import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||||
|
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||||
import { DashboardStartDependencies } from '../plugin';
|
import { DashboardStartDependencies } from '../plugin';
|
||||||
import {
|
import {
|
||||||
|
ACTION_ADD_SECTION,
|
||||||
ACTION_ADD_TO_LIBRARY,
|
ACTION_ADD_TO_LIBRARY,
|
||||||
ACTION_CLONE_PANEL,
|
ACTION_CLONE_PANEL,
|
||||||
ACTION_COPY_TO_DASHBOARD,
|
ACTION_COPY_TO_DASHBOARD,
|
||||||
|
@ -40,6 +42,12 @@ export const registerActions = async (plugins: DashboardStartDependencies) => {
|
||||||
});
|
});
|
||||||
uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, BADGE_FILTERS_NOTIFICATION);
|
uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, BADGE_FILTERS_NOTIFICATION);
|
||||||
|
|
||||||
|
uiActions.registerActionAsync(ACTION_ADD_SECTION, async () => {
|
||||||
|
const { AddSectionAction } = await import('../dashboard_renderer/dashboard_module');
|
||||||
|
return new AddSectionAction();
|
||||||
|
});
|
||||||
|
uiActions.attachAction(ADD_PANEL_TRIGGER, ACTION_ADD_SECTION);
|
||||||
|
|
||||||
if (share) {
|
if (share) {
|
||||||
uiActions.registerActionAsync(ACTION_EXPORT_CSV, async () => {
|
uiActions.registerActionAsync(ACTION_EXPORT_CSV, async () => {
|
||||||
const { ExportCSVAction } = await import('../dashboard_renderer/dashboard_module');
|
const { ExportCSVAction } = await import('../dashboard_renderer/dashboard_module');
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import deepEqual from 'fast-deep-equal';
|
||||||
|
import { xor } from 'lodash';
|
||||||
|
import { DashboardLayout } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the layouts have the same keys, and if they do, checks whether every layout item in the
|
||||||
|
* original layout is deep equal to the layout item at the same ID in the new layout
|
||||||
|
*/
|
||||||
|
export const areLayoutsEqual = (originalLayout?: DashboardLayout, newLayout?: DashboardLayout) => {
|
||||||
|
/**
|
||||||
|
* It is safe to assume that there are **usually** more panels than sections, so do cheaper section ID comparison first
|
||||||
|
*/
|
||||||
|
const newSectionUuids = Object.keys(newLayout?.sections ?? {});
|
||||||
|
const sectionIdDiff = xor(Object.keys(originalLayout?.sections ?? {}), newSectionUuids);
|
||||||
|
if (sectionIdDiff.length > 0) return false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since section IDs are equal, check for more expensive panel ID equality
|
||||||
|
*/
|
||||||
|
const newPanelUuids = Object.keys(newLayout?.panels ?? {});
|
||||||
|
const panelIdDiff = xor(Object.keys(originalLayout?.panels ?? {}), newPanelUuids);
|
||||||
|
if (panelIdDiff.length > 0) return false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDs of all widgets are equal, so now actually compare contents - this is the most expensive equality comparison step
|
||||||
|
*/
|
||||||
|
// again, start with section comparison since it is most likely cheaper
|
||||||
|
for (const sectionId of newSectionUuids) {
|
||||||
|
if (!deepEqual(originalLayout?.sections[sectionId], newLayout?.sections[sectionId])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// then compare panel grid data
|
||||||
|
for (const embeddableId of newPanelUuids) {
|
||||||
|
if (
|
||||||
|
!deepEqual(
|
||||||
|
originalLayout?.panels[embeddableId]?.gridData,
|
||||||
|
newLayout?.panels[embeddableId]?.gridData
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the "Elastic License
|
|
||||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
||||||
* Public License v 1"; you may not use this file except in compliance with, at
|
|
||||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { xor } from 'lodash';
|
|
||||||
import deepEqual from 'fast-deep-equal';
|
|
||||||
import { DashboardLayout } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the panel maps have the same keys, and if they do, whether the grid data and types of each panel
|
|
||||||
* are equal.
|
|
||||||
*/
|
|
||||||
export const arePanelLayoutsEqual = (
|
|
||||||
originalPanels?: DashboardLayout,
|
|
||||||
newPanels?: DashboardLayout
|
|
||||||
) => {
|
|
||||||
const originalUuids = Object.keys(originalPanels ?? {});
|
|
||||||
const newUuids = Object.keys(newPanels ?? {});
|
|
||||||
|
|
||||||
const idDiff = xor(originalUuids, newUuids);
|
|
||||||
if (idDiff.length > 0) return false;
|
|
||||||
|
|
||||||
for (const embeddableId of newUuids) {
|
|
||||||
if (originalPanels?.[embeddableId]?.type !== newPanels?.[embeddableId]?.type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!deepEqual(originalPanels?.[embeddableId]?.gridData, newPanels?.[embeddableId]?.gridData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
|
@ -16,6 +16,7 @@ export const DEFAULT_DASHBOARD_STATE: DashboardState = {
|
||||||
description: '',
|
description: '',
|
||||||
filters: [],
|
filters: [],
|
||||||
panels: {},
|
panels: {},
|
||||||
|
sections: {},
|
||||||
title: '',
|
title: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|
||||||
|
|
|
@ -11,18 +11,23 @@ import type { Reference } from '@kbn/content-management-utils';
|
||||||
import { EmbeddablePackageState } from '@kbn/embeddable-plugin/public';
|
import { EmbeddablePackageState } from '@kbn/embeddable-plugin/public';
|
||||||
import { BehaviorSubject, debounceTime, merge } from 'rxjs';
|
import { BehaviorSubject, debounceTime, merge } from 'rxjs';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
import { DASHBOARD_APP_ID } from '../../common/constants';
|
||||||
import {
|
import {
|
||||||
getReferencesForControls,
|
getReferencesForControls,
|
||||||
getReferencesForPanelId,
|
getReferencesForPanelId,
|
||||||
} from '../../common/dashboard_container/persistable_state/dashboard_container_references';
|
} from '../../common/dashboard_container/persistable_state/dashboard_container_references';
|
||||||
import { DASHBOARD_APP_ID } from '../../common/constants';
|
import type { DashboardState } from '../../common/types';
|
||||||
import { getDashboardContentManagementService } from '../services/dashboard_content_management_service';
|
import { getDashboardContentManagementService } from '../services/dashboard_content_management_service';
|
||||||
import { LoadDashboardReturn } from '../services/dashboard_content_management_service/types';
|
import { LoadDashboardReturn } from '../services/dashboard_content_management_service/types';
|
||||||
|
import {
|
||||||
|
CONTROL_GROUP_EMBEDDABLE_ID,
|
||||||
|
initializeControlGroupManager,
|
||||||
|
} from './control_group_manager';
|
||||||
import { initializeDataLoadingManager } from './data_loading_manager';
|
import { initializeDataLoadingManager } from './data_loading_manager';
|
||||||
import { initializeDataViewsManager } from './data_views_manager';
|
import { initializeDataViewsManager } from './data_views_manager';
|
||||||
import { DEFAULT_DASHBOARD_STATE } from './default_dashboard_state';
|
import { DEFAULT_DASHBOARD_STATE } from './default_dashboard_state';
|
||||||
import { getSerializedState } from './get_serialized_state';
|
import { getSerializedState } from './get_serialized_state';
|
||||||
import { initializePanelsManager } from './panels_manager';
|
import { initializeLayoutManager } from './layout_manager';
|
||||||
import { openSaveModal } from './save_modal/open_save_modal';
|
import { openSaveModal } from './save_modal/open_save_modal';
|
||||||
import { initializeSearchSessionManager } from './search_sessions/search_session_manager';
|
import { initializeSearchSessionManager } from './search_sessions/search_session_manager';
|
||||||
import { initializeSettingsManager } from './settings_manager';
|
import { initializeSettingsManager } from './settings_manager';
|
||||||
|
@ -35,14 +40,9 @@ import {
|
||||||
DashboardCreationOptions,
|
DashboardCreationOptions,
|
||||||
DashboardInternalApi,
|
DashboardInternalApi,
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { DashboardState } from '../../common/types';
|
|
||||||
import { initializeUnifiedSearchManager } from './unified_search_manager';
|
import { initializeUnifiedSearchManager } from './unified_search_manager';
|
||||||
import { initializeUnsavedChangesManager } from './unsaved_changes_manager';
|
import { initializeUnsavedChangesManager } from './unsaved_changes_manager';
|
||||||
import { initializeViewModeManager } from './view_mode_manager';
|
import { initializeViewModeManager } from './view_mode_manager';
|
||||||
import {
|
|
||||||
CONTROL_GROUP_EMBEDDABLE_ID,
|
|
||||||
initializeControlGroupManager,
|
|
||||||
} from './control_group_manager';
|
|
||||||
|
|
||||||
export function getDashboardApi({
|
export function getDashboardApi({
|
||||||
creationOptions,
|
creationOptions,
|
||||||
|
@ -63,7 +63,7 @@ export function getDashboardApi({
|
||||||
|
|
||||||
const viewModeManager = initializeViewModeManager(incomingEmbeddable, savedObjectResult);
|
const viewModeManager = initializeViewModeManager(incomingEmbeddable, savedObjectResult);
|
||||||
const trackPanel = initializeTrackPanel(async (id: string) => {
|
const trackPanel = initializeTrackPanel(async (id: string) => {
|
||||||
await panelsManager.api.getChildApi(id);
|
await layoutManager.api.getChildApi(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const references$ = new BehaviorSubject<Reference[] | undefined>(initialState.references);
|
const references$ = new BehaviorSubject<Reference[] | undefined>(initialState.references);
|
||||||
|
@ -78,9 +78,10 @@ export function getDashboardApi({
|
||||||
return panelReferences.length > 0 ? panelReferences : references$.value ?? [];
|
return panelReferences.length > 0 ? panelReferences : references$.value ?? [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const panelsManager = initializePanelsManager(
|
const layoutManager = initializeLayoutManager(
|
||||||
incomingEmbeddable,
|
incomingEmbeddable,
|
||||||
initialState.panels,
|
initialState.panels,
|
||||||
|
initialState.sections,
|
||||||
trackPanel,
|
trackPanel,
|
||||||
getReferences
|
getReferences
|
||||||
);
|
);
|
||||||
|
@ -88,10 +89,10 @@ export function getDashboardApi({
|
||||||
initialState.controlGroupInput,
|
initialState.controlGroupInput,
|
||||||
getReferences
|
getReferences
|
||||||
);
|
);
|
||||||
const dataLoadingManager = initializeDataLoadingManager(panelsManager.api.children$);
|
const dataLoadingManager = initializeDataLoadingManager(layoutManager.api.children$);
|
||||||
const dataViewsManager = initializeDataViewsManager(
|
const dataViewsManager = initializeDataViewsManager(
|
||||||
controlGroupManager.api.controlGroupApi$,
|
controlGroupManager.api.controlGroupApi$,
|
||||||
panelsManager.api.children$
|
layoutManager.api.children$
|
||||||
);
|
);
|
||||||
const settingsManager = initializeSettingsManager(initialState);
|
const settingsManager = initializeSettingsManager(initialState);
|
||||||
const unifiedSearchManager = initializeUnifiedSearchManager(
|
const unifiedSearchManager = initializeUnifiedSearchManager(
|
||||||
|
@ -107,7 +108,7 @@ export function getDashboardApi({
|
||||||
creationOptions,
|
creationOptions,
|
||||||
controlGroupManager,
|
controlGroupManager,
|
||||||
lastSavedState: savedObjectResult?.dashboardInput ?? DEFAULT_DASHBOARD_STATE,
|
lastSavedState: savedObjectResult?.dashboardInput ?? DEFAULT_DASHBOARD_STATE,
|
||||||
panelsManager,
|
layoutManager,
|
||||||
savedObjectId$,
|
savedObjectId$,
|
||||||
settingsManager,
|
settingsManager,
|
||||||
unifiedSearchManager,
|
unifiedSearchManager,
|
||||||
|
@ -115,13 +116,18 @@ export function getDashboardApi({
|
||||||
});
|
});
|
||||||
|
|
||||||
function getState() {
|
function getState() {
|
||||||
const { panels, references: panelReferences } = panelsManager.internalApi.serializePanels();
|
const {
|
||||||
|
panels,
|
||||||
|
sections,
|
||||||
|
references: panelReferences,
|
||||||
|
} = layoutManager.internalApi.serializeLayout();
|
||||||
const { state: unifiedSearchState, references: searchSourceReferences } =
|
const { state: unifiedSearchState, references: searchSourceReferences } =
|
||||||
unifiedSearchManager.internalApi.getState();
|
unifiedSearchManager.internalApi.getState();
|
||||||
const dashboardState: DashboardState = {
|
const dashboardState: DashboardState = {
|
||||||
...settingsManager.api.getSettings(),
|
...settingsManager.api.getSettings(),
|
||||||
...unifiedSearchState,
|
...unifiedSearchState,
|
||||||
panels,
|
panels,
|
||||||
|
sections,
|
||||||
viewMode: viewModeManager.api.viewMode$.value,
|
viewMode: viewModeManager.api.viewMode$.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -143,7 +149,7 @@ export function getDashboardApi({
|
||||||
...viewModeManager.api,
|
...viewModeManager.api,
|
||||||
...dataLoadingManager.api,
|
...dataLoadingManager.api,
|
||||||
...dataViewsManager.api,
|
...dataViewsManager.api,
|
||||||
...panelsManager.api,
|
...layoutManager.api,
|
||||||
...settingsManager.api,
|
...settingsManager.api,
|
||||||
...trackPanel,
|
...trackPanel,
|
||||||
...unifiedSearchManager.api,
|
...unifiedSearchManager.api,
|
||||||
|
@ -223,7 +229,7 @@ export function getDashboardApi({
|
||||||
getSerializedStateForChild: (childId: string) => {
|
getSerializedStateForChild: (childId: string) => {
|
||||||
return childId === CONTROL_GROUP_EMBEDDABLE_ID
|
return childId === CONTROL_GROUP_EMBEDDABLE_ID
|
||||||
? controlGroupManager.internalApi.getStateForControlGroup()
|
? controlGroupManager.internalApi.getStateForControlGroup()
|
||||||
: panelsManager.internalApi.getSerializedStateForPanel(childId);
|
: layoutManager.internalApi.getSerializedStateForPanel(childId);
|
||||||
},
|
},
|
||||||
setSavedObjectId: (id: string | undefined) => savedObjectId$.next(id),
|
setSavedObjectId: (id: string | undefined) => savedObjectId$.next(id),
|
||||||
type: DASHBOARD_API_TYPE as 'dashboard',
|
type: DASHBOARD_API_TYPE as 'dashboard',
|
||||||
|
@ -231,7 +237,7 @@ export function getDashboardApi({
|
||||||
} as Omit<DashboardApi, 'searchSessionId$'>;
|
} as Omit<DashboardApi, 'searchSessionId$'>;
|
||||||
|
|
||||||
const internalApi: DashboardInternalApi = {
|
const internalApi: DashboardInternalApi = {
|
||||||
...panelsManager.internalApi,
|
...layoutManager.internalApi,
|
||||||
...unifiedSearchManager.internalApi,
|
...unifiedSearchManager.internalApi,
|
||||||
setControlGroupApi: controlGroupManager.internalApi.setControlGroupApi,
|
setControlGroupApi: controlGroupManager.internalApi.setControlGroupApi,
|
||||||
};
|
};
|
||||||
|
|
|
@ -167,4 +167,70 @@ describe('getSerializedState', () => {
|
||||||
|
|
||||||
expect(result.references).toEqual(panelReferences);
|
expect(result.references).toEqual(panelReferences);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should serialize sections', () => {
|
||||||
|
const dashboardState = {
|
||||||
|
...getSampleDashboardState(),
|
||||||
|
panels: {
|
||||||
|
oldPanelId: {
|
||||||
|
type: 'visualization',
|
||||||
|
gridData: { sectionId: 'section1' },
|
||||||
|
} as unknown as DashboardPanelState,
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
section1: {
|
||||||
|
id: 'section1',
|
||||||
|
title: 'Section One',
|
||||||
|
collapsed: false,
|
||||||
|
gridData: { y: 1, i: 'section1' },
|
||||||
|
},
|
||||||
|
section2: {
|
||||||
|
id: 'section2',
|
||||||
|
title: 'Section Two',
|
||||||
|
collapsed: true,
|
||||||
|
gridData: { y: 2, i: 'section2' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = getSerializedState({
|
||||||
|
controlGroupReferences: [],
|
||||||
|
generateNewIds: true,
|
||||||
|
dashboardState,
|
||||||
|
panelReferences: [],
|
||||||
|
searchSourceReferences: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.attributes.panels).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"collapsed": false,
|
||||||
|
"gridData": Object {
|
||||||
|
"i": "section1",
|
||||||
|
"y": 1,
|
||||||
|
},
|
||||||
|
"panels": Array [
|
||||||
|
Object {
|
||||||
|
"gridData": Object {
|
||||||
|
"i": "54321",
|
||||||
|
},
|
||||||
|
"panelConfig": Object {},
|
||||||
|
"panelIndex": "54321",
|
||||||
|
"type": "visualization",
|
||||||
|
"version": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"title": "Section One",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"collapsed": true,
|
||||||
|
"gridData": Object {
|
||||||
|
"i": "section2",
|
||||||
|
"y": 2,
|
||||||
|
},
|
||||||
|
"panels": Array [],
|
||||||
|
"title": "Section Two",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,27 +7,30 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { RefreshInterval } from '@kbn/data-plugin/public';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import moment, { Moment } from 'moment';
|
import moment, { Moment } from 'moment';
|
||||||
import { RefreshInterval } from '@kbn/data-plugin/public';
|
|
||||||
|
|
||||||
import type { Reference } from '@kbn/content-management-utils';
|
import type { Reference } from '@kbn/content-management-utils';
|
||||||
|
import { extractReferences } from '../../common/dashboard_saved_object/persistable_state/dashboard_saved_object_references';
|
||||||
import {
|
import {
|
||||||
convertPanelMapToPanelsArray,
|
convertPanelSectionMapsToPanelsArray,
|
||||||
generateNewPanelIds,
|
generateNewPanelIds,
|
||||||
} from '../../common/lib/dashboard_panel_converters';
|
} from '../../common/lib/dashboard_panel_converters';
|
||||||
import { extractReferences } from '../../common/dashboard_saved_object/persistable_state/dashboard_saved_object_references';
|
|
||||||
import type { DashboardAttributes } from '../../server';
|
import type { DashboardAttributes } from '../../server';
|
||||||
|
|
||||||
import { convertDashboardVersionToNumber } from '../services/dashboard_content_management_service/lib/dashboard_versioning';
|
import type { DashboardState } from '../../common';
|
||||||
|
import { LATEST_VERSION } from '../../common/content_management';
|
||||||
|
import {
|
||||||
|
convertDashboardVersionToNumber,
|
||||||
|
convertNumberToDashboardVersion,
|
||||||
|
} from '../services/dashboard_content_management_service/lib/dashboard_versioning';
|
||||||
import {
|
import {
|
||||||
dataService,
|
dataService,
|
||||||
embeddableService,
|
embeddableService,
|
||||||
savedObjectsTaggingService,
|
savedObjectsTaggingService,
|
||||||
} from '../services/kibana_services';
|
} from '../services/kibana_services';
|
||||||
import type { DashboardState } from '../../common';
|
import { DashboardApi } from './types';
|
||||||
import { LATEST_VERSION } from '../../common/content_management';
|
|
||||||
import { convertNumberToDashboardVersion } from '../services/dashboard_content_management_service/lib/dashboard_versioning';
|
|
||||||
|
|
||||||
const LATEST_DASHBOARD_CONTAINER_VERSION = convertNumberToDashboardVersion(LATEST_VERSION);
|
const LATEST_DASHBOARD_CONTAINER_VERSION = convertNumberToDashboardVersion(LATEST_VERSION);
|
||||||
|
|
||||||
|
@ -53,13 +56,12 @@ export const getSerializedState = ({
|
||||||
dashboardState: DashboardState;
|
dashboardState: DashboardState;
|
||||||
panelReferences?: Reference[];
|
panelReferences?: Reference[];
|
||||||
searchSourceReferences?: Reference[];
|
searchSourceReferences?: Reference[];
|
||||||
}) => {
|
}): ReturnType<DashboardApi['getSerializedState']> => {
|
||||||
const {
|
const {
|
||||||
query: {
|
query: {
|
||||||
timefilter: { timefilter },
|
timefilter: { timefilter },
|
||||||
},
|
},
|
||||||
} = dataService;
|
} = dataService;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
tags,
|
tags,
|
||||||
query,
|
query,
|
||||||
|
@ -67,6 +69,7 @@ export const getSerializedState = ({
|
||||||
filters,
|
filters,
|
||||||
timeRestore,
|
timeRestore,
|
||||||
description,
|
description,
|
||||||
|
sections,
|
||||||
|
|
||||||
// Dashboard options
|
// Dashboard options
|
||||||
useMargins,
|
useMargins,
|
||||||
|
@ -100,7 +103,7 @@ export const getSerializedState = ({
|
||||||
syncTooltips,
|
syncTooltips,
|
||||||
hidePanelTitles,
|
hidePanelTitles,
|
||||||
};
|
};
|
||||||
const savedPanels = convertPanelMapToPanelsArray(panels, true);
|
const savedPanels = convertPanelSectionMapsToPanelsArray(panels, sections, true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse global time filter settings
|
* Parse global time filter settings
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { filter, map as lodashMap, max } from 'lodash';
|
||||||
|
import { BehaviorSubject, Observable, combineLatestWith, debounceTime, map, merge } from 'rxjs';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { METRIC_TYPE } from '@kbn/analytics';
|
import { METRIC_TYPE } from '@kbn/analytics';
|
||||||
import type { Reference } from '@kbn/content-management-utils';
|
import type { Reference } from '@kbn/content-management-utils';
|
||||||
import {
|
import {
|
||||||
|
@ -14,12 +18,8 @@ import {
|
||||||
EmbeddablePackageState,
|
EmbeddablePackageState,
|
||||||
PanelNotFoundError,
|
PanelNotFoundError,
|
||||||
} from '@kbn/embeddable-plugin/public';
|
} from '@kbn/embeddable-plugin/public';
|
||||||
import {
|
import { i18n } from '@kbn/i18n';
|
||||||
CanDuplicatePanels,
|
import { PanelPackage } from '@kbn/presentation-containers';
|
||||||
HasSerializedChildState,
|
|
||||||
PanelPackage,
|
|
||||||
PresentationContainer,
|
|
||||||
} from '@kbn/presentation-containers';
|
|
||||||
import {
|
import {
|
||||||
SerializedPanelState,
|
SerializedPanelState,
|
||||||
SerializedTitles,
|
SerializedTitles,
|
||||||
|
@ -32,10 +32,8 @@ import {
|
||||||
shouldLogStateDiff,
|
shouldLogStateDiff,
|
||||||
} from '@kbn/presentation-publishing';
|
} from '@kbn/presentation-publishing';
|
||||||
import { asyncForEach } from '@kbn/std';
|
import { asyncForEach } from '@kbn/std';
|
||||||
import { filter, map as lodashMap, max } from 'lodash';
|
|
||||||
import { BehaviorSubject, Observable, combineLatestWith, debounceTime, map, merge } from 'rxjs';
|
import type { DashboardSectionMap, DashboardState } from '../../common';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import type { DashboardState } from '../../common';
|
|
||||||
import { DashboardPanelMap } from '../../common';
|
import { DashboardPanelMap } from '../../common';
|
||||||
import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../common/content_management';
|
import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../common/content_management';
|
||||||
import { prefixReferencesFromPanel } from '../../common/dashboard_container/persistable_state/dashboard_container_references';
|
import { prefixReferencesFromPanel } from '../../common/dashboard_container/persistable_state/dashboard_container_references';
|
||||||
|
@ -47,83 +45,84 @@ import { runPanelPlacementStrategy } from '../panel_placement/place_new_panel_st
|
||||||
import { PanelPlacementStrategy } from '../plugin_constants';
|
import { PanelPlacementStrategy } from '../plugin_constants';
|
||||||
import { coreServices, usageCollectionService } from '../services/kibana_services';
|
import { coreServices, usageCollectionService } from '../services/kibana_services';
|
||||||
import { DASHBOARD_UI_METRIC_ID } from '../utils/telemetry_constants';
|
import { DASHBOARD_UI_METRIC_ID } from '../utils/telemetry_constants';
|
||||||
import { arePanelLayoutsEqual } from './are_panel_layouts_equal';
|
import { areLayoutsEqual } from './are_layouts_equal';
|
||||||
import type { initializeTrackPanel } from './track_panel';
|
import type { initializeTrackPanel } from './track_panel';
|
||||||
import {
|
import { DashboardChildState, DashboardChildren, DashboardLayout, DashboardPanel } from './types';
|
||||||
DashboardApi,
|
|
||||||
DashboardChildState,
|
|
||||||
DashboardChildren,
|
|
||||||
DashboardLayout,
|
|
||||||
DashboardLayoutItem,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
export function initializePanelsManager(
|
export function initializeLayoutManager(
|
||||||
incomingEmbeddable: EmbeddablePackageState | undefined,
|
incomingEmbeddable: EmbeddablePackageState | undefined,
|
||||||
initialPanels: DashboardPanelMap, // SERIALIZED STATE ONLY TODO Remove the DashboardPanelMap layer. We could take the Saved Dashboard Panels array here directly.
|
initialPanels: DashboardPanelMap, // SERIALIZED STATE ONLY TODO Remove the DashboardPanelMap layer. We could take the Saved Dashboard Panels array here directly.
|
||||||
|
initialSections: DashboardSectionMap,
|
||||||
trackPanel: ReturnType<typeof initializeTrackPanel>,
|
trackPanel: ReturnType<typeof initializeTrackPanel>,
|
||||||
getReferences: (id: string) => Reference[]
|
getReferences: (id: string) => Reference[]
|
||||||
): {
|
) {
|
||||||
internalApi: {
|
|
||||||
startComparing$: (
|
|
||||||
lastSavedState$: BehaviorSubject<DashboardState>
|
|
||||||
) => Observable<{ panels?: DashboardPanelMap }>;
|
|
||||||
getSerializedStateForPanel: HasSerializedChildState['getSerializedStateForChild'];
|
|
||||||
layout$: BehaviorSubject<DashboardLayout>;
|
|
||||||
registerChildApi: (api: DefaultEmbeddableApi) => void;
|
|
||||||
resetPanels: (lastSavedPanels: DashboardPanelMap) => void;
|
|
||||||
setChildState: (uuid: string, state: SerializedPanelState<object>) => void;
|
|
||||||
serializePanels: () => { panels: DashboardPanelMap; references: Reference[] };
|
|
||||||
};
|
|
||||||
api: PresentationContainer<DefaultEmbeddableApi> &
|
|
||||||
CanDuplicatePanels & { getDashboardPanelFromId: DashboardApi['getDashboardPanelFromId'] };
|
|
||||||
} {
|
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
// Set up panel state manager
|
// Set up panel state manager
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
const children$ = new BehaviorSubject<DashboardChildren>({});
|
const children$ = new BehaviorSubject<DashboardChildren>({});
|
||||||
const { layout: initialLayout, childState: initialChildState } = deserializePanels(initialPanels);
|
const { layout: initialLayout, childState: initialChildState } = deserializeLayout(
|
||||||
|
initialPanels,
|
||||||
|
initialSections
|
||||||
|
);
|
||||||
const layout$ = new BehaviorSubject<DashboardLayout>(initialLayout); // layout is the source of truth for which panels are in the dashboard.
|
const layout$ = new BehaviorSubject<DashboardLayout>(initialLayout); // layout is the source of truth for which panels are in the dashboard.
|
||||||
let currentChildState = initialChildState; // childState is the source of truth for the state of each panel.
|
let currentChildState = initialChildState; // childState is the source of truth for the state of each panel.
|
||||||
|
|
||||||
function deserializePanels(panelMap: DashboardPanelMap) {
|
function deserializeLayout(panelMap: DashboardPanelMap, sectionMap: DashboardSectionMap) {
|
||||||
const layout: DashboardLayout = {};
|
const layout: DashboardLayout = {
|
||||||
|
panels: {},
|
||||||
|
sections: {},
|
||||||
|
};
|
||||||
const childState: DashboardChildState = {};
|
const childState: DashboardChildState = {};
|
||||||
Object.keys(panelMap).forEach((uuid) => {
|
Object.keys(sectionMap).forEach((sectionId) => {
|
||||||
const { gridData, explicitInput, type } = panelMap[uuid];
|
layout.sections[sectionId] = { collapsed: false, ...sectionMap[sectionId] };
|
||||||
layout[uuid] = { type, gridData };
|
});
|
||||||
childState[uuid] = {
|
Object.keys(panelMap).forEach((panelId) => {
|
||||||
|
const { gridData, explicitInput, type } = panelMap[panelId];
|
||||||
|
layout.panels[panelId] = { type, gridData } as DashboardPanel;
|
||||||
|
childState[panelId] = {
|
||||||
rawState: explicitInput,
|
rawState: explicitInput,
|
||||||
references: getReferences(uuid),
|
references: getReferences(panelId),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return { layout, childState };
|
return { layout, childState };
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializePanels = (): { references: Reference[]; panels: DashboardPanelMap } => {
|
const serializeLayout = (): {
|
||||||
|
references: Reference[];
|
||||||
|
panels: DashboardPanelMap;
|
||||||
|
sections: DashboardSectionMap;
|
||||||
|
} => {
|
||||||
const references: Reference[] = [];
|
const references: Reference[] = [];
|
||||||
|
const layout = layout$.value;
|
||||||
const panels: DashboardPanelMap = {};
|
const panels: DashboardPanelMap = {};
|
||||||
for (const uuid of Object.keys(layout$.value)) {
|
|
||||||
|
for (const panelId of Object.keys(layout.panels)) {
|
||||||
references.push(
|
references.push(
|
||||||
...prefixReferencesFromPanel(uuid, currentChildState[uuid]?.references ?? [])
|
...prefixReferencesFromPanel(panelId, currentChildState[panelId]?.references ?? [])
|
||||||
);
|
);
|
||||||
panels[uuid] = {
|
panels[panelId] = {
|
||||||
...layout$.value[uuid],
|
...layout.panels[panelId],
|
||||||
explicitInput: currentChildState[uuid]?.rawState ?? {},
|
explicitInput: currentChildState[panelId]?.rawState ?? {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { panels, references };
|
return { panels, sections: { ...layout.sections }, references };
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetPanels = (lastSavedPanels: DashboardPanelMap) => {
|
const resetLayout = ({
|
||||||
const { layout: lastSavedLayout, childState: lstSavedChildState } =
|
panels: lastSavedPanels,
|
||||||
deserializePanels(lastSavedPanels);
|
sections: lastSavedSections,
|
||||||
|
}: DashboardState) => {
|
||||||
|
const { layout: lastSavedLayout, childState: lastSavedChildState } = deserializeLayout(
|
||||||
|
lastSavedPanels,
|
||||||
|
lastSavedSections
|
||||||
|
);
|
||||||
|
|
||||||
layout$.next(lastSavedLayout);
|
layout$.next(lastSavedLayout);
|
||||||
currentChildState = lstSavedChildState;
|
currentChildState = lastSavedChildState;
|
||||||
let childrenModified = false;
|
let childrenModified = false;
|
||||||
const currentChildren = { ...children$.value };
|
const currentChildren = { ...children$.value };
|
||||||
for (const uuid of Object.keys(currentChildren)) {
|
for (const uuid of Object.keys(currentChildren)) {
|
||||||
if (lastSavedLayout[uuid]) {
|
if (lastSavedLayout.panels[uuid]) {
|
||||||
const child = currentChildren[uuid];
|
const child = currentChildren[uuid];
|
||||||
if (apiPublishesUnsavedChanges(child)) child.resetUnsavedChanges();
|
if (apiPublishesUnsavedChanges(child)) child.resetUnsavedChanges();
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,7 +144,7 @@ export function initializePanelsManager(
|
||||||
{
|
{
|
||||||
width: size?.width ?? DEFAULT_PANEL_WIDTH,
|
width: size?.width ?? DEFAULT_PANEL_WIDTH,
|
||||||
height: size?.height ?? DEFAULT_PANEL_HEIGHT,
|
height: size?.height ?? DEFAULT_PANEL_HEIGHT,
|
||||||
currentPanels: layout$.value,
|
currentPanels: layout$.value.panels,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return { ...newPanelPlacement, i: uuid };
|
return { ...newPanelPlacement, i: uuid };
|
||||||
|
@ -154,11 +153,17 @@ export function initializePanelsManager(
|
||||||
const placeNewPanel = async (
|
const placeNewPanel = async (
|
||||||
uuid: string,
|
uuid: string,
|
||||||
panelPackage: PanelPackage,
|
panelPackage: PanelPackage,
|
||||||
gridData?: DashboardLayoutItem['gridData']
|
gridData?: DashboardPanel['gridData']
|
||||||
): Promise<DashboardLayout> => {
|
): Promise<DashboardLayout> => {
|
||||||
const { panelType: type, serializedState } = panelPackage;
|
const { panelType: type, serializedState } = panelPackage;
|
||||||
if (gridData) {
|
if (gridData) {
|
||||||
return { ...layout$.value, [uuid]: { gridData: { ...gridData, i: uuid }, type } };
|
return {
|
||||||
|
...layout$.value,
|
||||||
|
panels: {
|
||||||
|
...layout$.value.panels,
|
||||||
|
[uuid]: { gridData: { ...gridData, i: uuid }, type } as DashboardPanel,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const getCustomPlacementSettingFunc = getDashboardPanelPlacementSetting(type);
|
const getCustomPlacementSettingFunc = getDashboardPanelPlacementSetting(type);
|
||||||
const customPlacementSettings = getCustomPlacementSettingFunc
|
const customPlacementSettings = getCustomPlacementSettingFunc
|
||||||
|
@ -167,12 +172,18 @@ export function initializePanelsManager(
|
||||||
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
customPlacementSettings?.strategy ?? PanelPlacementStrategy.findTopLeftMostOpenSpace,
|
customPlacementSettings?.strategy ?? PanelPlacementStrategy.findTopLeftMostOpenSpace,
|
||||||
{
|
{
|
||||||
currentPanels: layout$.value,
|
currentPanels: layout$.value.panels,
|
||||||
height: customPlacementSettings?.height ?? DEFAULT_PANEL_HEIGHT,
|
height: customPlacementSettings?.height ?? DEFAULT_PANEL_HEIGHT,
|
||||||
width: customPlacementSettings?.width ?? DEFAULT_PANEL_WIDTH,
|
width: customPlacementSettings?.width ?? DEFAULT_PANEL_WIDTH,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return { ...otherPanels, [uuid]: { gridData: { ...newPanelPlacement, i: uuid }, type } };
|
return {
|
||||||
|
...layout$.value,
|
||||||
|
panels: {
|
||||||
|
...otherPanels,
|
||||||
|
[uuid]: { gridData: { ...newPanelPlacement, i: uuid }, type } as DashboardPanel,
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
|
@ -181,7 +192,7 @@ export function initializePanelsManager(
|
||||||
if (incomingEmbeddable) {
|
if (incomingEmbeddable) {
|
||||||
const { serializedState, size, type } = incomingEmbeddable;
|
const { serializedState, size, type } = incomingEmbeddable;
|
||||||
const uuid = incomingEmbeddable.embeddableId ?? v4();
|
const uuid = incomingEmbeddable.embeddableId ?? v4();
|
||||||
const existingPanel: DashboardLayoutItem | undefined = layout$.value[uuid];
|
const existingPanel: DashboardPanel | undefined = layout$.value.panels[uuid];
|
||||||
const sameType = existingPanel?.type === type;
|
const sameType = existingPanel?.type === type;
|
||||||
|
|
||||||
const gridData = existingPanel ? existingPanel.gridData : placeIncomingPanel(uuid, size);
|
const gridData = existingPanel ? existingPanel.gridData : placeIncomingPanel(uuid, size);
|
||||||
|
@ -195,14 +206,17 @@ export function initializePanelsManager(
|
||||||
|
|
||||||
layout$.next({
|
layout$.next({
|
||||||
...layout$.value,
|
...layout$.value,
|
||||||
[uuid]: { gridData, type },
|
panels: {
|
||||||
|
...layout$.value.panels,
|
||||||
|
[uuid]: { gridData, type } as DashboardPanel,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
trackPanel.setScrollToPanelId(uuid);
|
trackPanel.setScrollToPanelId(uuid);
|
||||||
trackPanel.setHighlightPanelId(uuid);
|
trackPanel.setHighlightPanelId(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDashboardPanelFromId(panelId: string) {
|
function getDashboardPanelFromId(panelId: string) {
|
||||||
const childLayout = layout$.value[panelId];
|
const childLayout = layout$.value.panels[panelId];
|
||||||
const childApi = children$.value[panelId];
|
const childApi = children$.value[panelId];
|
||||||
if (!childApi || !childLayout) throw new PanelNotFoundError();
|
if (!childApi || !childLayout) throw new PanelNotFoundError();
|
||||||
return {
|
return {
|
||||||
|
@ -216,7 +230,7 @@ export function initializePanelsManager(
|
||||||
|
|
||||||
async function getPanelTitles(): Promise<string[]> {
|
async function getPanelTitles(): Promise<string[]> {
|
||||||
const titles: string[] = [];
|
const titles: string[] = [];
|
||||||
await asyncForEach(Object.keys(layout$.value), async (id) => {
|
await asyncForEach(Object.keys(layout$.value.panels), async (id) => {
|
||||||
const childApi = await getChildApi(id);
|
const childApi = await getChildApi(id);
|
||||||
const title = apiPublishesTitle(childApi) ? getTitle(childApi) : '';
|
const title = apiPublishesTitle(childApi) ? getTitle(childApi) : '';
|
||||||
if (title) titles.push(title);
|
if (title) titles.push(title);
|
||||||
|
@ -230,7 +244,7 @@ export function initializePanelsManager(
|
||||||
const addNewPanel = async <ApiType>(
|
const addNewPanel = async <ApiType>(
|
||||||
panelPackage: PanelPackage,
|
panelPackage: PanelPackage,
|
||||||
displaySuccessMessage?: boolean,
|
displaySuccessMessage?: boolean,
|
||||||
gridData?: DashboardLayoutItem['gridData']
|
gridData?: DashboardPanel['gridData']
|
||||||
) => {
|
) => {
|
||||||
const uuid = v4();
|
const uuid = v4();
|
||||||
const { panelType: type, serializedState } = panelPackage;
|
const { panelType: type, serializedState } = panelPackage;
|
||||||
|
@ -246,17 +260,17 @@ export function initializePanelsManager(
|
||||||
title: getPanelAddedSuccessString(title),
|
title: getPanelAddedSuccessString(title),
|
||||||
'data-test-subj': 'addEmbeddableToDashboardSuccess',
|
'data-test-subj': 'addEmbeddableToDashboardSuccess',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
trackPanel.setScrollToPanelId(uuid);
|
trackPanel.setScrollToPanelId(uuid);
|
||||||
trackPanel.setHighlightPanelId(uuid);
|
trackPanel.setHighlightPanelId(uuid);
|
||||||
}
|
|
||||||
return (await getChildApi(uuid)) as ApiType;
|
return (await getChildApi(uuid)) as ApiType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removePanel = (uuid: string) => {
|
const removePanel = (uuid: string) => {
|
||||||
const layout = { ...layout$.value };
|
const panels = { ...layout$.value.panels };
|
||||||
if (layout[uuid]) {
|
if (panels[uuid]) {
|
||||||
delete layout[uuid];
|
delete panels[uuid];
|
||||||
layout$.next(layout);
|
layout$.next({ ...layout$.value, panels });
|
||||||
}
|
}
|
||||||
const children = { ...children$.value };
|
const children = { ...children$.value };
|
||||||
if (children[uuid]) {
|
if (children[uuid]) {
|
||||||
|
@ -269,7 +283,7 @@ export function initializePanelsManager(
|
||||||
};
|
};
|
||||||
|
|
||||||
const replacePanel = async (idToRemove: string, panelPackage: PanelPackage) => {
|
const replacePanel = async (idToRemove: string, panelPackage: PanelPackage) => {
|
||||||
const existingGridData = layout$.value[idToRemove]?.gridData;
|
const existingGridData = layout$.value.panels[idToRemove]?.gridData;
|
||||||
if (!existingGridData) throw new PanelNotFoundError();
|
if (!existingGridData) throw new PanelNotFoundError();
|
||||||
|
|
||||||
removePanel(idToRemove);
|
removePanel(idToRemove);
|
||||||
|
@ -278,7 +292,7 @@ export function initializePanelsManager(
|
||||||
};
|
};
|
||||||
|
|
||||||
const duplicatePanel = async (uuidToDuplicate: string) => {
|
const duplicatePanel = async (uuidToDuplicate: string) => {
|
||||||
const layoutItemToDuplicate = layout$.value[uuidToDuplicate];
|
const layoutItemToDuplicate = layout$.value.panels[uuidToDuplicate];
|
||||||
const apiToDuplicate = children$.value[uuidToDuplicate];
|
const apiToDuplicate = children$.value[uuidToDuplicate];
|
||||||
if (!apiToDuplicate || !layoutItemToDuplicate) throw new PanelNotFoundError();
|
if (!apiToDuplicate || !layoutItemToDuplicate) throw new PanelNotFoundError();
|
||||||
|
|
||||||
|
@ -297,15 +311,23 @@ export function initializePanelsManager(
|
||||||
const { newPanelPlacement, otherPanels } = placeClonePanel({
|
const { newPanelPlacement, otherPanels } = placeClonePanel({
|
||||||
width: layoutItemToDuplicate.gridData.w,
|
width: layoutItemToDuplicate.gridData.w,
|
||||||
height: layoutItemToDuplicate.gridData.h,
|
height: layoutItemToDuplicate.gridData.h,
|
||||||
currentPanels: layout$.value,
|
sectionId: layoutItemToDuplicate.gridData.sectionId,
|
||||||
|
currentPanels: layout$.value.panels,
|
||||||
placeBesideId: uuidToDuplicate,
|
placeBesideId: uuidToDuplicate,
|
||||||
});
|
});
|
||||||
layout$.next({
|
layout$.next({
|
||||||
|
...layout$.value,
|
||||||
|
panels: {
|
||||||
...otherPanels,
|
...otherPanels,
|
||||||
[uuidOfDuplicate]: {
|
[uuidOfDuplicate]: {
|
||||||
gridData: { ...newPanelPlacement, i: uuidOfDuplicate },
|
gridData: {
|
||||||
|
...newPanelPlacement,
|
||||||
|
i: uuidOfDuplicate,
|
||||||
|
sectionId: layoutItemToDuplicate.gridData.sectionId,
|
||||||
|
},
|
||||||
type: layoutItemToDuplicate.type,
|
type: layoutItemToDuplicate.type,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
coreServices.notifications.toasts.addSuccess({
|
coreServices.notifications.toasts.addSuccess({
|
||||||
|
@ -315,7 +337,7 @@ export function initializePanelsManager(
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChildApi = async (uuid: string): Promise<DefaultEmbeddableApi | undefined> => {
|
const getChildApi = async (uuid: string): Promise<DefaultEmbeddableApi | undefined> => {
|
||||||
if (!layout$.value[uuid]) throw new PanelNotFoundError();
|
if (!layout$.value.panels[uuid]) throw new PanelNotFoundError();
|
||||||
if (children$.value[uuid]) return children$.value[uuid];
|
if (children$.value[uuid]) return children$.value[uuid];
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
@ -326,7 +348,7 @@ export function initializePanelsManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we hit this, the panel was removed before the embeddable finished loading.
|
// If we hit this, the panel was removed before the embeddable finished loading.
|
||||||
if (layout$.value[uuid] === undefined) {
|
if (layout$.value.panels[uuid] === undefined) {
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
}
|
}
|
||||||
|
@ -338,25 +360,34 @@ export function initializePanelsManager(
|
||||||
internalApi: {
|
internalApi: {
|
||||||
getSerializedStateForPanel: (uuid: string) => currentChildState[uuid],
|
getSerializedStateForPanel: (uuid: string) => currentChildState[uuid],
|
||||||
layout$,
|
layout$,
|
||||||
resetPanels,
|
reset: resetLayout,
|
||||||
serializePanels,
|
serializeLayout,
|
||||||
startComparing$: (
|
startComparing$: (
|
||||||
lastSavedState$: BehaviorSubject<DashboardState>
|
lastSavedState$: BehaviorSubject<DashboardState>
|
||||||
): Observable<{ panels?: DashboardPanelMap }> => {
|
): Observable<{ panels?: DashboardPanelMap; sections?: DashboardSectionMap }> => {
|
||||||
return layout$.pipe(
|
return layout$.pipe(
|
||||||
debounceTime(100),
|
debounceTime(100),
|
||||||
combineLatestWith(lastSavedState$.pipe(map((lastSaved) => lastSaved.panels))),
|
combineLatestWith(
|
||||||
map(([, lastSavedPanels]) => {
|
lastSavedState$.pipe(
|
||||||
const panels = serializePanels().panels;
|
map((lastSaved) => ({ panels: lastSaved.panels, sections: lastSaved.sections }))
|
||||||
if (!arePanelLayoutsEqual(lastSavedPanels, panels)) {
|
)
|
||||||
|
),
|
||||||
|
map(([, { panels: lastSavedPanels, sections: lastSavedSections }]) => {
|
||||||
|
const { panels, sections } = serializeLayout();
|
||||||
|
if (
|
||||||
|
!areLayoutsEqual(
|
||||||
|
{ panels: lastSavedPanels, sections: lastSavedSections },
|
||||||
|
{ panels, sections }
|
||||||
|
)
|
||||||
|
) {
|
||||||
if (shouldLogStateDiff()) {
|
if (shouldLogStateDiff()) {
|
||||||
logStateDiff(
|
logStateDiff(
|
||||||
'dashboard layout',
|
'dashboard layout',
|
||||||
deserializePanels(lastSavedPanels).layout,
|
deserializeLayout(lastSavedPanels, lastSavedSections).layout,
|
||||||
deserializePanels(panels).layout
|
deserializeLayout(panels, sections).layout
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { panels };
|
return { panels, sections };
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
})
|
})
|
||||||
|
@ -371,8 +402,13 @@ export function initializePanelsManager(
|
||||||
setChildState: (uuid: string, state: SerializedPanelState<object>) => {
|
setChildState: (uuid: string, state: SerializedPanelState<object>) => {
|
||||||
currentChildState[uuid] = state;
|
currentChildState[uuid] = state;
|
||||||
},
|
},
|
||||||
|
isSectionCollapsed: (sectionId?: string): boolean => {
|
||||||
|
const { sections } = layout$.getValue();
|
||||||
|
return Boolean(sectionId && sections[sectionId].collapsed);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
api: {
|
api: {
|
||||||
|
/** Panels */
|
||||||
children$,
|
children$,
|
||||||
getChildApi,
|
getChildApi,
|
||||||
addNewPanel,
|
addNewPanel,
|
||||||
|
@ -380,8 +416,39 @@ export function initializePanelsManager(
|
||||||
replacePanel,
|
replacePanel,
|
||||||
duplicatePanel,
|
duplicatePanel,
|
||||||
getDashboardPanelFromId,
|
getDashboardPanelFromId,
|
||||||
getPanelCount: () => Object.keys(layout$.value).length,
|
getPanelCount: () => Object.keys(layout$.value.panels).length,
|
||||||
canRemovePanels: () => trackPanel.expandedPanelId$.value === undefined,
|
canRemovePanels: () => trackPanel.expandedPanelId$.value === undefined,
|
||||||
|
|
||||||
|
/** Sections */
|
||||||
|
addNewSection: () => {
|
||||||
|
const currentLayout = layout$.getValue();
|
||||||
|
|
||||||
|
// find the max y so we know where to add the section
|
||||||
|
let maxY = 0;
|
||||||
|
[...Object.values(currentLayout.panels), ...Object.values(currentLayout.sections)].forEach(
|
||||||
|
(widget) => {
|
||||||
|
const { y, h } = { h: 1, ...widget.gridData };
|
||||||
|
maxY = Math.max(maxY, y + h);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// add the new section
|
||||||
|
const sections = { ...currentLayout.sections };
|
||||||
|
const newId = v4();
|
||||||
|
sections[newId] = {
|
||||||
|
id: newId,
|
||||||
|
gridData: { i: newId, y: maxY },
|
||||||
|
title: i18n.translate('dashboard.defaultSectionTitle', {
|
||||||
|
defaultMessage: 'New collapsible section',
|
||||||
|
}),
|
||||||
|
collapsed: false,
|
||||||
|
};
|
||||||
|
layout$.next({
|
||||||
|
...currentLayout,
|
||||||
|
sections,
|
||||||
|
});
|
||||||
|
trackPanel.scrollToBottom$.next();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -7,13 +7,14 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, Subject } from 'rxjs';
|
||||||
|
|
||||||
export function initializeTrackPanel(untilLoaded: (id: string) => Promise<undefined>) {
|
export function initializeTrackPanel(untilLoaded: (id: string) => Promise<undefined>) {
|
||||||
const expandedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
const expandedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
||||||
const focusedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
const focusedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
||||||
const highlightPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
const highlightPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
||||||
const scrollToPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
const scrollToPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
||||||
|
const scrollToBottom$ = new Subject<void>();
|
||||||
let scrollPosition: number | undefined;
|
let scrollPosition: number | undefined;
|
||||||
|
|
||||||
function setScrollToPanelId(id: string | undefined) {
|
function setScrollToPanelId(id: string | undefined) {
|
||||||
|
@ -62,15 +63,19 @@ export function initializeTrackPanel(untilLoaded: (id: string) => Promise<undefi
|
||||||
untilLoaded(id).then(() => {
|
untilLoaded(id).then(() => {
|
||||||
setScrollToPanelId(undefined);
|
setScrollToPanelId(undefined);
|
||||||
if (scrollPosition !== undefined) {
|
if (scrollPosition !== undefined) {
|
||||||
window.scrollTo({ top: scrollPosition });
|
window.scrollTo({ top: scrollPosition, behavior: 'smooth' });
|
||||||
scrollPosition = undefined;
|
scrollPosition = undefined;
|
||||||
} else {
|
} else {
|
||||||
panelRef.scrollIntoView({ block: 'start' });
|
panelRef.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
scrollToTop: () => {
|
scrollToTop: () => {
|
||||||
window.scroll(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
},
|
||||||
|
scrollToBottom$,
|
||||||
|
scrollToBottom: () => {
|
||||||
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||||
},
|
},
|
||||||
setFocusedPanelId: (id: string | undefined) => {
|
setFocusedPanelId: (id: string | undefined) => {
|
||||||
if (focusedPanelId$.value !== id) focusedPanelId$.next(id);
|
if (focusedPanelId$.value !== id) focusedPanelId$.next(id);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||||
import { PublishesESQLVariables } from '@kbn/esql-types';
|
import { PublishesESQLVariables } from '@kbn/esql-types';
|
||||||
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import {
|
import {
|
||||||
|
CanAddNewSection,
|
||||||
CanExpandPanels,
|
CanExpandPanels,
|
||||||
HasLastSavedChildState,
|
HasLastSavedChildState,
|
||||||
HasSerializedChildState,
|
HasSerializedChildState,
|
||||||
|
@ -47,6 +48,8 @@ import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
DashboardLocatorParams,
|
DashboardLocatorParams,
|
||||||
DashboardPanelMap,
|
DashboardPanelMap,
|
||||||
|
DashboardPanelState,
|
||||||
|
DashboardSectionMap,
|
||||||
DashboardSettings,
|
DashboardSettings,
|
||||||
DashboardState,
|
DashboardState,
|
||||||
} from '../../common';
|
} from '../../common';
|
||||||
|
@ -58,9 +61,12 @@ import {
|
||||||
|
|
||||||
export const DASHBOARD_API_TYPE = 'dashboard';
|
export const DASHBOARD_API_TYPE = 'dashboard';
|
||||||
|
|
||||||
export type DashboardLayoutItem = { gridData: GridData } & HasType;
|
export const ReservedLayoutItemTypes: readonly string[] = ['section'] as const;
|
||||||
|
|
||||||
|
export type DashboardPanel = Pick<DashboardPanelState, 'gridData'> & HasType;
|
||||||
export interface DashboardLayout {
|
export interface DashboardLayout {
|
||||||
[uuid: string]: DashboardLayoutItem;
|
panels: { [uuid: string]: DashboardPanel }; // partial of DashboardPanelState
|
||||||
|
sections: DashboardSectionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardChildState {
|
export interface DashboardChildState {
|
||||||
|
@ -102,6 +108,7 @@ export interface DashboardCreationOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DashboardApi = CanExpandPanels &
|
export type DashboardApi = CanExpandPanels &
|
||||||
|
CanAddNewSection &
|
||||||
HasAppContext &
|
HasAppContext &
|
||||||
HasExecutionContext &
|
HasExecutionContext &
|
||||||
HasLastSavedChildState &
|
HasLastSavedChildState &
|
||||||
|
@ -151,6 +158,8 @@ export type DashboardApi = CanExpandPanels &
|
||||||
scrollToPanel: (panelRef: HTMLDivElement) => void;
|
scrollToPanel: (panelRef: HTMLDivElement) => void;
|
||||||
scrollToPanelId$: PublishingSubject<string | undefined>;
|
scrollToPanelId$: PublishingSubject<string | undefined>;
|
||||||
scrollToTop: () => void;
|
scrollToTop: () => void;
|
||||||
|
scrollToBottom: () => void;
|
||||||
|
scrollToBottom$: Subject<void>;
|
||||||
setFilters: (filters?: Filter[] | undefined) => void;
|
setFilters: (filters?: Filter[] | undefined) => void;
|
||||||
setFullScreenMode: (fullScreenMode: boolean) => void;
|
setFullScreenMode: (fullScreenMode: boolean) => void;
|
||||||
setHighlightPanelId: (id: string | undefined) => void;
|
setHighlightPanelId: (id: string | undefined) => void;
|
||||||
|
@ -168,5 +177,10 @@ export interface DashboardInternalApi {
|
||||||
layout$: BehaviorSubject<DashboardLayout>;
|
layout$: BehaviorSubject<DashboardLayout>;
|
||||||
registerChildApi: (api: DefaultEmbeddableApi) => void;
|
registerChildApi: (api: DefaultEmbeddableApi) => void;
|
||||||
setControlGroupApi: (controlGroupApi: ControlGroupApi) => void;
|
setControlGroupApi: (controlGroupApi: ControlGroupApi) => void;
|
||||||
serializePanels: () => { references: Reference[]; panels: DashboardPanelMap };
|
serializeLayout: () => {
|
||||||
|
references: Reference[];
|
||||||
|
panels: DashboardPanelMap;
|
||||||
|
sections: DashboardSectionMap;
|
||||||
|
};
|
||||||
|
isSectionCollapsed: (sectionId?: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
tap,
|
tap,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { getDashboardBackupService } from '../services/dashboard_backup_service';
|
import { getDashboardBackupService } from '../services/dashboard_backup_service';
|
||||||
import { initializePanelsManager } from './panels_manager';
|
import { initializeLayoutManager } from './layout_manager';
|
||||||
import { initializeSettingsManager } from './settings_manager';
|
import { initializeSettingsManager } from './settings_manager';
|
||||||
import { DashboardCreationOptions } from './types';
|
import { DashboardCreationOptions } from './types';
|
||||||
import { DashboardState } from '../../common';
|
import { DashboardState } from '../../common';
|
||||||
|
@ -40,7 +40,7 @@ import {
|
||||||
const DEBOUNCE_TIME = 100;
|
const DEBOUNCE_TIME = 100;
|
||||||
|
|
||||||
export function initializeUnsavedChangesManager({
|
export function initializeUnsavedChangesManager({
|
||||||
panelsManager,
|
layoutManager,
|
||||||
savedObjectId$,
|
savedObjectId$,
|
||||||
lastSavedState,
|
lastSavedState,
|
||||||
settingsManager,
|
settingsManager,
|
||||||
|
@ -55,7 +55,7 @@ export function initializeUnsavedChangesManager({
|
||||||
getReferences: (id: string) => Reference[];
|
getReferences: (id: string) => Reference[];
|
||||||
savedObjectId$: PublishesSavedObjectId['savedObjectId$'];
|
savedObjectId$: PublishesSavedObjectId['savedObjectId$'];
|
||||||
controlGroupManager: ReturnType<typeof initializeControlGroupManager>;
|
controlGroupManager: ReturnType<typeof initializeControlGroupManager>;
|
||||||
panelsManager: ReturnType<typeof initializePanelsManager>;
|
layoutManager: ReturnType<typeof initializeLayoutManager>;
|
||||||
viewModeManager: ReturnType<typeof initializeViewModeManager>;
|
viewModeManager: ReturnType<typeof initializeViewModeManager>;
|
||||||
settingsManager: ReturnType<typeof initializeSettingsManager>;
|
settingsManager: ReturnType<typeof initializeSettingsManager>;
|
||||||
unifiedSearchManager: ReturnType<typeof initializeUnifiedSearchManager>;
|
unifiedSearchManager: ReturnType<typeof initializeUnifiedSearchManager>;
|
||||||
|
@ -75,14 +75,14 @@ export function initializeUnsavedChangesManager({
|
||||||
// references injected while loading dashboard saved object in loadDashboardState
|
// references injected while loading dashboard saved object in loadDashboardState
|
||||||
const lastSavedState$ = new BehaviorSubject<DashboardState>(lastSavedState);
|
const lastSavedState$ = new BehaviorSubject<DashboardState>(lastSavedState);
|
||||||
|
|
||||||
const hasPanelChanges$ = childrenUnsavedChanges$(panelsManager.api.children$).pipe(
|
const hasPanelChanges$ = childrenUnsavedChanges$(layoutManager.api.children$).pipe(
|
||||||
tap((childrenWithChanges) => {
|
tap((childrenWithChanges) => {
|
||||||
// propagate the latest serialized state back to the panels manager.
|
// propagate the latest serialized state back to the layout manager.
|
||||||
for (const { uuid, hasUnsavedChanges } of childrenWithChanges) {
|
for (const { uuid, hasUnsavedChanges } of childrenWithChanges) {
|
||||||
const childApi = panelsManager.api.children$.value[uuid];
|
const childApi = layoutManager.api.children$.value[uuid];
|
||||||
if (!hasUnsavedChanges || !childApi || !apiHasSerializableState(childApi)) continue;
|
if (!hasUnsavedChanges || !childApi || !apiHasSerializableState(childApi)) continue;
|
||||||
|
|
||||||
panelsManager.internalApi.setChildState(uuid, childApi.serializeState());
|
layoutManager.internalApi.setChildState(uuid, childApi.serializeState());
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
map((childrenWithChanges) => {
|
map((childrenWithChanges) => {
|
||||||
|
@ -93,7 +93,7 @@ export function initializeUnsavedChangesManager({
|
||||||
const dashboardStateChanges$: Observable<Partial<DashboardState>> = combineLatest([
|
const dashboardStateChanges$: Observable<Partial<DashboardState>> = combineLatest([
|
||||||
settingsManager.internalApi.startComparing$(lastSavedState$),
|
settingsManager.internalApi.startComparing$(lastSavedState$),
|
||||||
unifiedSearchManager.internalApi.startComparing$(lastSavedState$),
|
unifiedSearchManager.internalApi.startComparing$(lastSavedState$),
|
||||||
panelsManager.internalApi.startComparing$(lastSavedState$),
|
layoutManager.internalApi.startComparing$(lastSavedState$),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([settings, unifiedSearch, panels]) => {
|
map(([settings, unifiedSearch, panels]) => {
|
||||||
return { ...settings, ...unifiedSearch, ...panels };
|
return { ...settings, ...unifiedSearch, ...panels };
|
||||||
|
@ -129,10 +129,9 @@ export function initializeUnsavedChangesManager({
|
||||||
|
|
||||||
// always back up view mode. This allows us to know which Dashboards were last changed while in edit mode.
|
// always back up view mode. This allows us to know which Dashboards were last changed while in edit mode.
|
||||||
dashboardStateToBackup.viewMode = viewMode;
|
dashboardStateToBackup.viewMode = viewMode;
|
||||||
|
|
||||||
// Backup latest state from children that have unsaved changes
|
// Backup latest state from children that have unsaved changes
|
||||||
if (hasPanelChanges || hasControlGroupChanges) {
|
if (hasPanelChanges || hasControlGroupChanges) {
|
||||||
const { panels, references } = panelsManager.internalApi.serializePanels();
|
const { panels, references } = layoutManager.internalApi.serializeLayout();
|
||||||
const { controlGroupInput, controlGroupReferences } =
|
const { controlGroupInput, controlGroupReferences } =
|
||||||
controlGroupManager.internalApi.serializeControlGroup();
|
controlGroupManager.internalApi.serializeControlGroup();
|
||||||
// dashboardStateToBackup.references will be used instead of savedObjectResult.references
|
// dashboardStateToBackup.references will be used instead of savedObjectResult.references
|
||||||
|
@ -169,9 +168,10 @@ export function initializeUnsavedChangesManager({
|
||||||
return {
|
return {
|
||||||
api: {
|
api: {
|
||||||
asyncResetToLastSavedState: async () => {
|
asyncResetToLastSavedState: async () => {
|
||||||
panelsManager.internalApi.resetPanels(lastSavedState$.value.panels);
|
const savedState = lastSavedState$.value;
|
||||||
unifiedSearchManager.internalApi.reset(lastSavedState$.value);
|
layoutManager.internalApi.reset(savedState);
|
||||||
settingsManager.internalApi.reset(lastSavedState$.value);
|
unifiedSearchManager.internalApi.reset(savedState);
|
||||||
|
settingsManager.internalApi.reset(savedState);
|
||||||
|
|
||||||
await controlGroupManager.api.controlGroupApi$.value?.resetUnsavedChanges();
|
await controlGroupManager.api.controlGroupApi$.value?.resetUnsavedChanges();
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Capabilities } from '@kbn/core/public';
|
import { Capabilities } from '@kbn/core/public';
|
||||||
import { convertPanelMapToPanelsArray } from '../../../../common/lib/dashboard_panel_converters';
|
import { convertPanelSectionMapsToPanelsArray } from '../../../../common/lib/dashboard_panel_converters';
|
||||||
import { DashboardLocatorParams } from '../../../../common/types';
|
import { DashboardLocatorParams } from '../../../../common/types';
|
||||||
import { getDashboardBackupService } from '../../../services/dashboard_backup_service';
|
import { getDashboardBackupService } from '../../../services/dashboard_backup_service';
|
||||||
import { shareService } from '../../../services/kibana_services';
|
import { shareService } from '../../../services/kibana_services';
|
||||||
|
@ -124,7 +124,7 @@ describe('ShowShareModal', () => {
|
||||||
).locatorParams.params;
|
).locatorParams.params;
|
||||||
const rawDashboardState = {
|
const rawDashboardState = {
|
||||||
...unsavedDashboardState,
|
...unsavedDashboardState,
|
||||||
panels: convertPanelMapToPanelsArray(unsavedDashboardState.panels),
|
panels: convertPanelSectionMapsToPanelsArray(unsavedDashboardState.panels, {}),
|
||||||
};
|
};
|
||||||
unsavedStateKeys.forEach((key) => {
|
unsavedStateKeys.forEach((key) => {
|
||||||
expect(shareLocatorParams[key]).toStrictEqual(
|
expect(shareLocatorParams[key]).toStrictEqual(
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import React, { ReactElement, useState } from 'react';
|
||||||
|
|
||||||
import { EuiCallOut, EuiCheckboxGroup } from '@elastic/eui';
|
import { EuiCallOut, EuiCheckboxGroup } from '@elastic/eui';
|
||||||
import type { Capabilities } from '@kbn/core/public';
|
import type { Capabilities } from '@kbn/core/public';
|
||||||
import { QueryState } from '@kbn/data-plugin/common';
|
import { QueryState } from '@kbn/data-plugin/common';
|
||||||
|
@ -14,12 +18,10 @@ import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { getStateFromKbnUrl, setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public';
|
import { getStateFromKbnUrl, setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public';
|
||||||
import { omit } from 'lodash';
|
|
||||||
import moment from 'moment';
|
|
||||||
import React, { ReactElement, useState } from 'react';
|
|
||||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||||
|
|
||||||
import { DashboardLocatorParams } from '../../../../common';
|
import { DashboardLocatorParams } from '../../../../common';
|
||||||
import { convertPanelMapToPanelsArray } from '../../../../common/lib/dashboard_panel_converters';
|
import { convertPanelSectionMapsToPanelsArray } from '../../../../common/lib/dashboard_panel_converters';
|
||||||
import { SharedDashboardState } from '../../../../common/types';
|
import { SharedDashboardState } from '../../../../common/types';
|
||||||
import { getDashboardBackupService } from '../../../services/dashboard_backup_service';
|
import { getDashboardBackupService } from '../../../services/dashboard_backup_service';
|
||||||
import { coreServices, dataService, shareService } from '../../../services/kibana_services';
|
import { coreServices, dataService, shareService } from '../../../services/kibana_services';
|
||||||
|
@ -110,8 +112,11 @@ export function ShowShareModal({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { panels: allUnsavedPanelsMap, ...unsavedDashboardState } =
|
const {
|
||||||
getDashboardBackupService().getState(savedObjectId) ?? {};
|
panels: allUnsavedPanelsMap,
|
||||||
|
sections: allUnsavedSectionsMap,
|
||||||
|
...unsavedDashboardState
|
||||||
|
} = getDashboardBackupService().getState(savedObjectId) ?? {};
|
||||||
|
|
||||||
const hasPanelChanges = allUnsavedPanelsMap !== undefined;
|
const hasPanelChanges = allUnsavedPanelsMap !== undefined;
|
||||||
|
|
||||||
|
@ -121,8 +126,11 @@ export function ShowShareModal({
|
||||||
unsavedDashboardState.controlGroupInput as SharedDashboardState['controlGroupInput'],
|
unsavedDashboardState.controlGroupInput as SharedDashboardState['controlGroupInput'],
|
||||||
references: unsavedDashboardState.references as SharedDashboardState['references'],
|
references: unsavedDashboardState.references as SharedDashboardState['references'],
|
||||||
};
|
};
|
||||||
if (allUnsavedPanelsMap) {
|
if (allUnsavedPanelsMap || allUnsavedSectionsMap) {
|
||||||
unsavedDashboardStateForLocator.panels = convertPanelMapToPanelsArray(allUnsavedPanelsMap);
|
unsavedDashboardStateForLocator.panels = convertPanelSectionMapsToPanelsArray(
|
||||||
|
allUnsavedPanelsMap ?? {},
|
||||||
|
allUnsavedSectionsMap ?? {}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const locatorParams: DashboardLocatorParams = {
|
const locatorParams: DashboardLocatorParams = {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import { History } from 'history';
|
import { History } from 'history';
|
||||||
import { map } from 'rxjs';
|
import { map } from 'rxjs';
|
||||||
import { SEARCH_SESSION_ID } from '../../../common/constants';
|
import { SEARCH_SESSION_ID } from '../../../common/constants';
|
||||||
import { convertPanelMapToPanelsArray } from '../../../common/lib/dashboard_panel_converters';
|
import { convertPanelSectionMapsToPanelsArray } from '../../../common/lib/dashboard_panel_converters';
|
||||||
import { DashboardLocatorParams } from '../../../common/types';
|
import { DashboardLocatorParams } from '../../../common/types';
|
||||||
import { DashboardApi, DashboardInternalApi } from '../../dashboard_api/types';
|
import { DashboardApi, DashboardInternalApi } from '../../dashboard_api/types';
|
||||||
import { dataService } from '../../services/kibana_services';
|
import { dataService } from '../../services/kibana_services';
|
||||||
|
@ -81,7 +81,7 @@ function getLocatorParams({
|
||||||
shouldRestoreSearchSession: boolean;
|
shouldRestoreSearchSession: boolean;
|
||||||
}): DashboardLocatorParams {
|
}): DashboardLocatorParams {
|
||||||
const savedObjectId = dashboardApi.savedObjectId$.value;
|
const savedObjectId = dashboardApi.savedObjectId$.value;
|
||||||
const { panels, references } = dashboardInternalApi.serializePanels();
|
const { panels, sections, references } = dashboardInternalApi.serializeLayout();
|
||||||
return {
|
return {
|
||||||
viewMode: dashboardApi.viewMode$.value ?? 'view',
|
viewMode: dashboardApi.viewMode$.value ?? 'view',
|
||||||
useHash: false,
|
useHash: false,
|
||||||
|
@ -104,7 +104,10 @@ function getLocatorParams({
|
||||||
...(savedObjectId
|
...(savedObjectId
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
panels: convertPanelMapToPanelsArray(panels) as DashboardLocatorParams['panels'],
|
panels: convertPanelSectionMapsToPanelsArray(
|
||||||
|
panels,
|
||||||
|
sections
|
||||||
|
) as DashboardLocatorParams['panels'],
|
||||||
references: references as DashboardLocatorParams['references'],
|
references: references as DashboardLocatorParams['references'],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,17 +7,22 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { serializeRuntimeState } from '@kbn/controls-plugin/public';
|
|
||||||
import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
|
||||||
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
|
||||||
import { History } from 'history';
|
import { History } from 'history';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { skip } from 'rxjs';
|
import { skip } from 'rxjs';
|
||||||
import semverSatisfies from 'semver/functions/satisfies';
|
import semverSatisfies from 'semver/functions/satisfies';
|
||||||
import type { DashboardPanelMap } from '../../../common/dashboard_container/types';
|
|
||||||
import { convertPanelsArrayToPanelMap } from '../../../common/lib/dashboard_panel_converters';
|
import { serializeRuntimeState } from '@kbn/controls-plugin/public';
|
||||||
|
import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common';
|
||||||
|
import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
DashboardPanelMap,
|
||||||
|
DashboardSectionMap,
|
||||||
|
} from '../../../common/dashboard_container/types';
|
||||||
|
import { convertPanelsArrayToPanelSectionMaps } from '../../../common/lib/dashboard_panel_converters';
|
||||||
import type { DashboardState, SharedDashboardState } from '../../../common/types';
|
import type { DashboardState, SharedDashboardState } from '../../../common/types';
|
||||||
import type { DashboardPanel } from '../../../server/content_management';
|
import type { DashboardPanel, DashboardSection } from '../../../server/content_management';
|
||||||
import type { SavedDashboardPanel } from '../../../server/dashboard_saved_object';
|
import type { SavedDashboardPanel } from '../../../server/dashboard_saved_object';
|
||||||
import { DashboardApi } from '../../dashboard_api/types';
|
import { DashboardApi } from '../../dashboard_api/types';
|
||||||
import { migrateLegacyQuery } from '../../services/dashboard_content_management_service/lib/load_dashboard_state';
|
import { migrateLegacyQuery } from '../../services/dashboard_content_management_service/lib/load_dashboard_state';
|
||||||
|
@ -33,8 +38,14 @@ const panelIsLegacy = (panel: unknown): panel is SavedDashboardPanel => {
|
||||||
* We no longer support loading panels from a version older than 7.3 in the URL.
|
* We no longer support loading panels from a version older than 7.3 in the URL.
|
||||||
* @returns whether or not there is a panel in the URL state saved with a version before 7.3
|
* @returns whether or not there is a panel in the URL state saved with a version before 7.3
|
||||||
*/
|
*/
|
||||||
export const isPanelVersionTooOld = (panels: DashboardPanel[] | SavedDashboardPanel[]) => {
|
export const isPanelVersionTooOld = (
|
||||||
|
panels: Array<DashboardPanel | DashboardSection> | SavedDashboardPanel[]
|
||||||
|
) => {
|
||||||
for (const panel of panels) {
|
for (const panel of panels) {
|
||||||
|
if ('panels' in panel) {
|
||||||
|
// can't use isDashboardSection type guard because of SavedDashboardPanel type
|
||||||
|
continue; // ignore sections
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!panel.gridData ||
|
!panel.gridData ||
|
||||||
!((panel as DashboardPanel).panelConfig || (panel as SavedDashboardPanel).embeddableConfig) ||
|
!((panel as DashboardPanel).panelConfig || (panel as SavedDashboardPanel).embeddableConfig) ||
|
||||||
|
@ -45,13 +56,15 @@ export const isPanelVersionTooOld = (panels: DashboardPanel[] | SavedDashboardPa
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPanelsMap(panels?: DashboardPanel[]): DashboardPanelMap | undefined {
|
function getPanelSectionMaps(
|
||||||
|
panels?: Array<DashboardPanel | DashboardSection>
|
||||||
|
): { panels: DashboardPanelMap; sections: DashboardSectionMap } | undefined {
|
||||||
if (!panels) {
|
if (!panels) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panels.length === 0) {
|
if (panels.length === 0) {
|
||||||
return {};
|
return { panels: {}, sections: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPanelVersionTooOld(panels)) {
|
if (isPanelVersionTooOld(panels)) {
|
||||||
|
@ -71,7 +84,7 @@ function getPanelsMap(panels?: DashboardPanel[]): DashboardPanelMap | undefined
|
||||||
return panel;
|
return panel;
|
||||||
});
|
});
|
||||||
|
|
||||||
return convertPanelsArrayToPanelMap(standardizedPanels);
|
return convertPanelsArrayToPanelSectionMaps(standardizedPanels);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,8 +98,7 @@ export const loadAndRemoveDashboardState = (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!rawAppStateInUrl) return {};
|
if (!rawAppStateInUrl) return {};
|
||||||
|
const converted = getPanelSectionMaps(rawAppStateInUrl.panels);
|
||||||
const panelsMap = getPanelsMap(rawAppStateInUrl.panels);
|
|
||||||
|
|
||||||
const nextUrl = replaceUrlHashQuery(window.location.href, (hashQuery) => {
|
const nextUrl = replaceUrlHashQuery(window.location.href, (hashQuery) => {
|
||||||
delete hashQuery[DASHBOARD_STATE_STORAGE_KEY];
|
delete hashQuery[DASHBOARD_STATE_STORAGE_KEY];
|
||||||
|
@ -100,7 +112,8 @@ export const loadAndRemoveDashboardState = (
|
||||||
controlGroupInput: serializeRuntimeState(rawAppStateInUrl.controlGroupState).rawState,
|
controlGroupInput: serializeRuntimeState(rawAppStateInUrl.controlGroupState).rawState,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(panelsMap ? { panels: panelsMap } : {}),
|
...(converted?.panels ? { panels: converted.panels } : {}),
|
||||||
|
...(converted?.sections ? { sections: converted.sections } : {}),
|
||||||
...(rawAppStateInUrl.query ? { query: migrateLegacyQuery(rawAppStateInUrl.query) } : {}),
|
...(rawAppStateInUrl.query ? { query: migrateLegacyQuery(rawAppStateInUrl.query) } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
.dshEmptyPromptParent {
|
.dshEmptyPromptParent {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dshEmptyPromptPageTemplate {
|
.dshEmptyPromptPageTemplate {
|
||||||
|
|
|
@ -17,3 +17,4 @@ export { ExportCSVAction } from '../dashboard_actions/export_csv_action';
|
||||||
export { AddToLibraryAction } from '../dashboard_actions/library_add_action';
|
export { AddToLibraryAction } from '../dashboard_actions/library_add_action';
|
||||||
export { UnlinkFromLibraryAction } from '../dashboard_actions/library_unlink_action';
|
export { UnlinkFromLibraryAction } from '../dashboard_actions/library_unlink_action';
|
||||||
export { CopyToDashboardAction } from '../dashboard_actions/copy_to_dashboard_action';
|
export { CopyToDashboardAction } from '../dashboard_actions/copy_to_dashboard_action';
|
||||||
|
export { AddSectionAction } from '../dashboard_actions/add_section_action';
|
||||||
|
|
|
@ -8,19 +8,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { EuiThemeProvider } from '@elastic/eui';
|
|
||||||
|
|
||||||
|
import { EuiThemeProvider } from '@elastic/eui';
|
||||||
import { useBatchedPublishingSubjects as mockUseBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
import { useBatchedPublishingSubjects as mockUseBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||||
import { DashboardPanelMap } from '../../../common';
|
import { RenderResult, act, getByLabelText, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import { DashboardPanelMap, DashboardSectionMap } from '../../../common';
|
||||||
import {
|
import {
|
||||||
DashboardContext,
|
DashboardContext,
|
||||||
useDashboardApi as mockUseDashboardApi,
|
useDashboardApi as mockUseDashboardApi,
|
||||||
} from '../../dashboard_api/use_dashboard_api';
|
} from '../../dashboard_api/use_dashboard_api';
|
||||||
import { DashboardInternalContext } from '../../dashboard_api/use_dashboard_internal_api';
|
import { DashboardInternalContext } from '../../dashboard_api/use_dashboard_internal_api';
|
||||||
import { buildMockDashboardApi } from '../../mocks';
|
import { buildMockDashboardApi, getMockDashboardPanels } from '../../mocks';
|
||||||
import { DashboardGrid } from './dashboard_grid';
|
import { DashboardGrid } from './dashboard_grid';
|
||||||
import type { Props as DashboardGridItemProps } from './dashboard_grid_item';
|
import type { Props as DashboardGridItemProps } from './dashboard_grid_item';
|
||||||
import { RenderResult, act, render, waitFor } from '@testing-library/react';
|
|
||||||
|
|
||||||
jest.mock('./dashboard_grid_item', () => {
|
jest.mock('./dashboard_grid_item', () => {
|
||||||
return {
|
return {
|
||||||
|
@ -56,19 +58,6 @@ jest.mock('./dashboard_grid_item', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const PANELS = {
|
|
||||||
'1': {
|
|
||||||
gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' },
|
|
||||||
type: 'lens',
|
|
||||||
explicitInput: { id: '1' },
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
gridData: { x: 6, y: 6, w: 6, h: 6, i: '2' },
|
|
||||||
type: 'lens',
|
|
||||||
explicitInput: { id: '2' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const verifyElementHasClass = (
|
const verifyElementHasClass = (
|
||||||
component: RenderResult,
|
component: RenderResult,
|
||||||
elementSelector: string,
|
elementSelector: string,
|
||||||
|
@ -79,10 +68,16 @@ const verifyElementHasClass = (
|
||||||
expect(itemToCheck!.classList.contains(className)).toBe(true);
|
expect(itemToCheck!.classList.contains(className)).toBe(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createAndMountDashboardGrid = async (panels: DashboardPanelMap = PANELS) => {
|
const createAndMountDashboardGrid = async (overrides?: {
|
||||||
|
panels?: DashboardPanelMap;
|
||||||
|
sections?: DashboardSectionMap;
|
||||||
|
}) => {
|
||||||
|
const panels = overrides?.panels ?? getMockDashboardPanels().panels;
|
||||||
|
const sections = overrides?.sections;
|
||||||
const { api, internalApi } = buildMockDashboardApi({
|
const { api, internalApi } = buildMockDashboardApi({
|
||||||
overrides: {
|
overrides: {
|
||||||
panels,
|
panels,
|
||||||
|
...(sections && { sections }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const component = render(
|
const component = render(
|
||||||
|
@ -95,35 +90,45 @@ const createAndMountDashboardGrid = async (panels: DashboardPanelMap = PANELS) =
|
||||||
</EuiThemeProvider>
|
</EuiThemeProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// panels in collapsed sections should not render
|
||||||
|
const panelRenderCount = sections
|
||||||
|
? Object.values(panels).filter((value) => {
|
||||||
|
const sectionId = value.gridData.sectionId;
|
||||||
|
return sectionId ? !sections[sectionId].collapsed : true;
|
||||||
|
}).length
|
||||||
|
: Object.keys(panels).length;
|
||||||
|
|
||||||
// wait for first render
|
// wait for first render
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(component.queryAllByTestId('dashboardGridItem').length).toBe(Object.keys(panels).length);
|
expect(component.queryAllByTestId('dashboardGridItem').length).toBe(panelRenderCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { dashboardApi: api, component };
|
return { dashboardApi: api, internalApi, component };
|
||||||
};
|
};
|
||||||
|
|
||||||
test('renders DashboardGrid', async () => {
|
describe('DashboardGrid', () => {
|
||||||
await createAndMountDashboardGrid(PANELS);
|
test('renders', async () => {
|
||||||
});
|
await createAndMountDashboardGrid();
|
||||||
|
});
|
||||||
|
|
||||||
test('renders DashboardGrid with no visualizations', async () => {
|
describe('panels', () => {
|
||||||
await createAndMountDashboardGrid({});
|
test('renders with no visualizations', async () => {
|
||||||
});
|
await createAndMountDashboardGrid();
|
||||||
|
});
|
||||||
|
|
||||||
test('DashboardGrid removes panel when removed from container', async () => {
|
test('removes panel when removed from container', async () => {
|
||||||
const { dashboardApi, component } = await createAndMountDashboardGrid(PANELS);
|
const { dashboardApi, component } = await createAndMountDashboardGrid();
|
||||||
|
|
||||||
// remove panel
|
// remove panel
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
dashboardApi.removePanel('1');
|
dashboardApi.removePanel('2');
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1));
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(component.getAllByTestId('dashboardGridItem').length).toBe(1);
|
expect(component.getAllByTestId('dashboardGridItem').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DashboardGrid renders expanded panel', async () => {
|
test('renders expanded panel', async () => {
|
||||||
const { dashboardApi, component } = await createAndMountDashboardGrid();
|
const { dashboardApi, component } = await createAndMountDashboardGrid();
|
||||||
|
|
||||||
// maximize panel
|
// maximize panel
|
||||||
|
@ -146,9 +151,9 @@ test('DashboardGrid renders expanded panel', async () => {
|
||||||
|
|
||||||
verifyElementHasClass(component, '#mockDashboardGridItem_1', 'regularPanel');
|
verifyElementHasClass(component, '#mockDashboardGridItem_1', 'regularPanel');
|
||||||
verifyElementHasClass(component, '#mockDashboardGridItem_2', 'regularPanel');
|
verifyElementHasClass(component, '#mockDashboardGridItem_2', 'regularPanel');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('DashboardGrid renders focused panel', async () => {
|
test('renders focused panel', async () => {
|
||||||
const { dashboardApi, component } = await createAndMountDashboardGrid();
|
const { dashboardApi, component } = await createAndMountDashboardGrid();
|
||||||
const overlayMock = {
|
const overlayMock = {
|
||||||
onClose: new Promise<void>((resolve) => {
|
onClose: new Promise<void>((resolve) => {
|
||||||
|
@ -175,4 +180,135 @@ test('DashboardGrid renders focused panel', async () => {
|
||||||
|
|
||||||
verifyElementHasClass(component, '#mockDashboardGridItem_1', 'regularPanel');
|
verifyElementHasClass(component, '#mockDashboardGridItem_1', 'regularPanel');
|
||||||
verifyElementHasClass(component, '#mockDashboardGridItem_2', 'regularPanel');
|
verifyElementHasClass(component, '#mockDashboardGridItem_2', 'regularPanel');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sections', () => {
|
||||||
|
test('renders sections', async () => {
|
||||||
|
const { panels, sections } = getMockDashboardPanels(true);
|
||||||
|
await createAndMountDashboardGrid({
|
||||||
|
panels,
|
||||||
|
sections,
|
||||||
|
});
|
||||||
|
|
||||||
|
const header1 = screen.getByTestId('kbnGridSectionHeader-section1');
|
||||||
|
expect(header1).toBeInTheDocument();
|
||||||
|
expect(header1.classList).toContain('kbnGridSectionHeader--collapsed');
|
||||||
|
const header2 = screen.getByTestId('kbnGridSectionHeader-section2');
|
||||||
|
expect(header2).toBeInTheDocument();
|
||||||
|
expect(header2.classList).not.toContain('kbnGridSectionHeader--collapsed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can add new section', async () => {
|
||||||
|
const { panels, sections } = getMockDashboardPanels(true);
|
||||||
|
const { dashboardApi, internalApi } = await createAndMountDashboardGrid({
|
||||||
|
panels,
|
||||||
|
sections,
|
||||||
|
});
|
||||||
|
dashboardApi.addNewSection();
|
||||||
|
await waitFor(() => {
|
||||||
|
const headers = screen.getAllByLabelText('Edit section title'); // aria-label
|
||||||
|
expect(headers.length).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
const newHeader = Object.values(internalApi.layout$.getValue().sections).filter(
|
||||||
|
({ gridData: { y } }) => y === 8
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
expect(newHeader.title).toEqual('New collapsible section');
|
||||||
|
expect(screen.getByText(newHeader.title)).toBeInTheDocument();
|
||||||
|
expect(newHeader.collapsed).toBe(false);
|
||||||
|
expect(screen.getByTestId(`kbnGridSectionHeader-${newHeader.id}`).classList).not.toContain(
|
||||||
|
'kbnGridSectionHeader--collapsed'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dashboard state updates on collapse', async () => {
|
||||||
|
const { panels, sections } = getMockDashboardPanels(true);
|
||||||
|
const { internalApi } = await createAndMountDashboardGrid({
|
||||||
|
panels,
|
||||||
|
sections,
|
||||||
|
});
|
||||||
|
|
||||||
|
const headerButton = screen.getByTestId(`kbnGridSectionTitle-section2`);
|
||||||
|
expect(headerButton.nodeName.toLowerCase()).toBe('button');
|
||||||
|
userEvent.click(headerButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(internalApi.layout$.getValue().sections.section2.collapsed).toBe(true);
|
||||||
|
});
|
||||||
|
expect(headerButton.getAttribute('aria-expanded')).toBe('false');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dashboard state updates on section deletion', async () => {
|
||||||
|
const { panels, sections } = getMockDashboardPanels(true, {
|
||||||
|
sections: {
|
||||||
|
emptySection: {
|
||||||
|
id: 'emptySection',
|
||||||
|
title: 'Empty section',
|
||||||
|
collapsed: false,
|
||||||
|
gridData: { i: 'emptySection', y: 8 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { internalApi } = await createAndMountDashboardGrid({
|
||||||
|
panels,
|
||||||
|
sections,
|
||||||
|
});
|
||||||
|
|
||||||
|
// can delete empty section
|
||||||
|
const deleteEmptySectionButton = getByLabelText(
|
||||||
|
screen.getByTestId('kbnGridSectionHeader-emptySection'),
|
||||||
|
'Delete section'
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
await userEvent.click(deleteEmptySectionButton);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(Object.keys(internalApi.layout$.getValue().sections)).not.toContain('emptySection');
|
||||||
|
});
|
||||||
|
|
||||||
|
// can delete non-empty section
|
||||||
|
const deleteSection1Button = getByLabelText(
|
||||||
|
screen.getByTestId('kbnGridSectionHeader-section1'),
|
||||||
|
'Delete section'
|
||||||
|
);
|
||||||
|
await userEvent.click(deleteSection1Button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('kbnGridLayoutDeleteSectionModal-section1')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirmDeleteButton = screen.getByText('Delete section and 1 panel');
|
||||||
|
await userEvent.click(confirmDeleteButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(Object.keys(internalApi.layout$.getValue().sections)).not.toContain('section1');
|
||||||
|
expect(Object.keys(internalApi.layout$.getValue().panels)).not.toContain('3'); // this is the panel in section1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('layout responds to dashboard state update', async () => {
|
||||||
|
const withoutSections = getMockDashboardPanels();
|
||||||
|
const withSections = getMockDashboardPanels(true);
|
||||||
|
|
||||||
|
const { internalApi } = await createAndMountDashboardGrid({
|
||||||
|
panels: withoutSections.panels,
|
||||||
|
sections: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
let sectionContainers = screen.getAllByTestId(`kbnGridSectionWrapper-`, {
|
||||||
|
exact: false,
|
||||||
|
});
|
||||||
|
expect(sectionContainers.length).toBe(1); // only the first top section is rendered
|
||||||
|
|
||||||
|
internalApi.layout$.next(withSections);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
sectionContainers = screen.getAllByTestId(`kbnGridSectionWrapper-`, {
|
||||||
|
exact: false,
|
||||||
|
});
|
||||||
|
expect(sectionContainers.length).toBe(2); // section wrappers are not rendered for collapsed sections
|
||||||
|
expect(screen.getAllByTestId('dashboardGridItem').length).toBe(3); // one panel is in a collapsed section
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,19 +10,20 @@
|
||||||
import { useEuiTheme } from '@elastic/eui';
|
import { useEuiTheme } from '@elastic/eui';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import { useAppFixedViewport } from '@kbn/core-rendering-browser';
|
import { useAppFixedViewport } from '@kbn/core-rendering-browser';
|
||||||
import { GridLayout, type GridLayoutData } from '@kbn/grid-layout';
|
import { GridLayout, GridPanelData, GridSectionData, type GridLayoutData } from '@kbn/grid-layout';
|
||||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import { default as React, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../common/content_management/constants';
|
import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../common/content_management/constants';
|
||||||
import { arePanelLayoutsEqual } from '../../dashboard_api/are_panel_layouts_equal';
|
import { GridData } from '../../../common/content_management/v2/types';
|
||||||
|
import { areLayoutsEqual } from '../../dashboard_api/are_layouts_equal';
|
||||||
import { DashboardLayout } from '../../dashboard_api/types';
|
import { DashboardLayout } from '../../dashboard_api/types';
|
||||||
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
||||||
import { useDashboardInternalApi } from '../../dashboard_api/use_dashboard_internal_api';
|
import { useDashboardInternalApi } from '../../dashboard_api/use_dashboard_internal_api';
|
||||||
import {
|
import {
|
||||||
DEFAULT_DASHBOARD_DRAG_TOP_OFFSET,
|
|
||||||
DASHBOARD_GRID_HEIGHT,
|
DASHBOARD_GRID_HEIGHT,
|
||||||
DASHBOARD_MARGIN_SIZE,
|
DASHBOARD_MARGIN_SIZE,
|
||||||
|
DEFAULT_DASHBOARD_DRAG_TOP_OFFSET,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { DashboardGridItem } from './dashboard_grid_item';
|
import { DashboardGridItem } from './dashboard_grid_item';
|
||||||
import { useLayoutStyles } from './use_layout_styles';
|
import { useLayoutStyles } from './use_layout_styles';
|
||||||
|
@ -34,11 +35,13 @@ export const DashboardGrid = ({
|
||||||
}) => {
|
}) => {
|
||||||
const dashboardApi = useDashboardApi();
|
const dashboardApi = useDashboardApi();
|
||||||
const dashboardInternalApi = useDashboardInternalApi();
|
const dashboardInternalApi = useDashboardInternalApi();
|
||||||
|
const layoutRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const layoutStyles = useLayoutStyles();
|
const layoutStyles = useLayoutStyles();
|
||||||
const panelRefs = useRef<{ [panelId: string]: React.Ref<HTMLDivElement> }>({});
|
const panelRefs = useRef<{ [panelId: string]: React.Ref<HTMLDivElement> }>({});
|
||||||
const { euiTheme } = useEuiTheme();
|
const { euiTheme } = useEuiTheme();
|
||||||
|
|
||||||
|
const [topOffset, setTopOffset] = useState(DEFAULT_DASHBOARD_DRAG_TOP_OFFSET);
|
||||||
const [expandedPanelId, layout, useMargins, viewMode] = useBatchedPublishingSubjects(
|
const [expandedPanelId, layout, useMargins, viewMode] = useBatchedPublishingSubjects(
|
||||||
dashboardApi.expandedPanelId$,
|
dashboardApi.expandedPanelId$,
|
||||||
dashboardInternalApi.layout$,
|
dashboardInternalApi.layout$,
|
||||||
|
@ -46,57 +49,93 @@ export const DashboardGrid = ({
|
||||||
dashboardApi.viewMode$
|
dashboardApi.viewMode$
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTopOffset(
|
||||||
|
dashboardContainerRef?.current?.getBoundingClientRect().top ??
|
||||||
|
DEFAULT_DASHBOARD_DRAG_TOP_OFFSET
|
||||||
|
);
|
||||||
|
}, [dashboardContainerRef]);
|
||||||
|
|
||||||
const appFixedViewport = useAppFixedViewport();
|
const appFixedViewport = useAppFixedViewport();
|
||||||
|
|
||||||
const currentLayout: GridLayoutData = useMemo(() => {
|
const currentLayout: GridLayoutData = useMemo(() => {
|
||||||
const singleRow: GridLayoutData = {};
|
const newLayout: GridLayoutData = {};
|
||||||
|
Object.keys(layout.sections).forEach((sectionId) => {
|
||||||
Object.keys(layout).forEach((panelId) => {
|
const section = layout.sections[sectionId];
|
||||||
const gridData = layout[panelId].gridData;
|
newLayout[sectionId] = {
|
||||||
singleRow[panelId] = {
|
id: sectionId,
|
||||||
|
type: 'section',
|
||||||
|
row: section.gridData.y,
|
||||||
|
isCollapsed: Boolean(section.collapsed),
|
||||||
|
title: section.title,
|
||||||
|
panels: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
Object.keys(layout.panels).forEach((panelId) => {
|
||||||
|
const gridData = layout.panels[panelId].gridData;
|
||||||
|
const basePanel = {
|
||||||
id: panelId,
|
id: panelId,
|
||||||
row: gridData.y,
|
row: gridData.y,
|
||||||
column: gridData.x,
|
column: gridData.x,
|
||||||
width: gridData.w,
|
width: gridData.w,
|
||||||
height: gridData.h,
|
height: gridData.h,
|
||||||
|
} as GridPanelData;
|
||||||
|
if (gridData.sectionId) {
|
||||||
|
(newLayout[gridData.sectionId] as GridSectionData).panels[panelId] = basePanel;
|
||||||
|
} else {
|
||||||
|
newLayout[panelId] = {
|
||||||
|
...basePanel,
|
||||||
type: 'panel',
|
type: 'panel',
|
||||||
};
|
};
|
||||||
|
}
|
||||||
// update `data-grid-row` attribute for all panels because it is used for some styling
|
// update `data-grid-row` attribute for all panels because it is used for some styling
|
||||||
const panelRef = panelRefs.current[panelId];
|
const panelRef = panelRefs.current[panelId];
|
||||||
if (typeof panelRef !== 'function' && panelRef?.current) {
|
if (typeof panelRef !== 'function' && panelRef?.current) {
|
||||||
panelRef.current.setAttribute('data-grid-row', `${gridData.y}`);
|
panelRef.current.setAttribute('data-grid-row', `${gridData.y}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return newLayout;
|
||||||
return singleRow;
|
|
||||||
}, [layout]);
|
}, [layout]);
|
||||||
|
|
||||||
const onLayoutChange = useCallback(
|
const onLayoutChange = useCallback(
|
||||||
(newLayout: GridLayoutData) => {
|
(newLayout: GridLayoutData) => {
|
||||||
if (viewMode !== 'edit') return;
|
if (viewMode !== 'edit') return;
|
||||||
|
|
||||||
const currentPanels = dashboardInternalApi.layout$.getValue();
|
const currLayout = dashboardInternalApi.layout$.getValue();
|
||||||
const updatedPanels: DashboardLayout = Object.values(newLayout).reduce(
|
const updatedLayout: DashboardLayout = {
|
||||||
(updatedPanelsAcc, widget) => {
|
sections: {},
|
||||||
|
panels: {},
|
||||||
|
};
|
||||||
|
Object.values(newLayout).forEach((widget) => {
|
||||||
if (widget.type === 'section') {
|
if (widget.type === 'section') {
|
||||||
return updatedPanelsAcc; // sections currently aren't supported
|
updatedLayout.sections[widget.id] = {
|
||||||
}
|
collapsed: widget.isCollapsed,
|
||||||
updatedPanelsAcc[widget.id] = {
|
title: widget.title,
|
||||||
...currentPanels[widget.id],
|
id: widget.id,
|
||||||
gridData: {
|
gridData: {
|
||||||
i: widget.id,
|
i: widget.id,
|
||||||
y: widget.row,
|
y: widget.row,
|
||||||
x: widget.column,
|
|
||||||
w: widget.width,
|
|
||||||
h: widget.height,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return updatedPanelsAcc;
|
Object.values(widget.panels).forEach((panel) => {
|
||||||
|
updatedLayout.panels[panel.id] = {
|
||||||
|
...currLayout.panels[panel.id],
|
||||||
|
gridData: {
|
||||||
|
...convertGridPanelToDashboardGridData(panel),
|
||||||
|
sectionId: widget.id,
|
||||||
},
|
},
|
||||||
{} as DashboardLayout
|
};
|
||||||
);
|
});
|
||||||
if (!arePanelLayoutsEqual(currentPanels, updatedPanels)) {
|
} else {
|
||||||
dashboardInternalApi.layout$.next(updatedPanels);
|
// widget is a panel
|
||||||
|
updatedLayout.panels[widget.id] = {
|
||||||
|
...currLayout.panels[widget.id],
|
||||||
|
gridData: convertGridPanelToDashboardGridData(widget),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!areLayoutsEqual(currLayout, updatedLayout)) {
|
||||||
|
dashboardInternalApi.layout$.next(updatedLayout);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dashboardInternalApi.layout$, viewMode]
|
[dashboardInternalApi.layout$, viewMode]
|
||||||
|
@ -104,13 +143,14 @@ export const DashboardGrid = ({
|
||||||
|
|
||||||
const renderPanelContents = useCallback(
|
const renderPanelContents = useCallback(
|
||||||
(id: string, setDragHandles: (refs: Array<HTMLElement | null>) => void) => {
|
(id: string, setDragHandles: (refs: Array<HTMLElement | null>) => void) => {
|
||||||
const currentPanels = dashboardInternalApi.layout$.getValue();
|
const panels = dashboardInternalApi.layout$.getValue().panels;
|
||||||
if (!currentPanels[id]) return;
|
if (!panels[id]) return;
|
||||||
|
|
||||||
if (!panelRefs.current[id]) {
|
if (!panelRefs.current[id]) {
|
||||||
panelRefs.current[id] = React.createRef();
|
panelRefs.current[id] = React.createRef();
|
||||||
}
|
}
|
||||||
const type = currentPanels[id].type;
|
|
||||||
|
const type = panels[id].type;
|
||||||
return (
|
return (
|
||||||
<DashboardGridItem
|
<DashboardGridItem
|
||||||
ref={panelRefs.current[id]}
|
ref={panelRefs.current[id]}
|
||||||
|
@ -120,13 +160,45 @@ export const DashboardGrid = ({
|
||||||
setDragHandles={setDragHandles}
|
setDragHandles={setDragHandles}
|
||||||
appFixedViewport={appFixedViewport}
|
appFixedViewport={appFixedViewport}
|
||||||
dashboardContainerRef={dashboardContainerRef}
|
dashboardContainerRef={dashboardContainerRef}
|
||||||
data-grid-row={currentPanels[id].gridData.y} // initialize data-grid-row
|
data-grid-row={panels[id].gridData.y} // initialize data-grid-row
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[appFixedViewport, dashboardContainerRef, dashboardInternalApi.layout$]
|
[appFixedViewport, dashboardContainerRef, dashboardInternalApi.layout$]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/**
|
||||||
|
* ResizeObserver fires the callback on `.observe()` with the initial size of the observed
|
||||||
|
* element; we want to ignore this first call and scroll to the bottom on the **second**
|
||||||
|
* callback - i.e. after the row is actually added to the DOM
|
||||||
|
*/
|
||||||
|
let first = false;
|
||||||
|
const scrollToBottomOnResize = new ResizeObserver(() => {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
dashboardApi.scrollToBottom();
|
||||||
|
scrollToBottomOnResize.disconnect(); // once scrolled, stop observing resize events
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `scrollToBottom$` emits, wait for the `layoutRef` size to change then scroll
|
||||||
|
* to the bottom of the screen
|
||||||
|
*/
|
||||||
|
const scrollToBottomSubscription = dashboardApi.scrollToBottom$.subscribe(() => {
|
||||||
|
if (!layoutRef.current) return;
|
||||||
|
first = true; // ensure that only the second resize callback actually triggers scrolling
|
||||||
|
scrollToBottomOnResize.observe(layoutRef.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
scrollToBottomOnResize.disconnect();
|
||||||
|
scrollToBottomSubscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [dashboardApi]);
|
||||||
|
|
||||||
const memoizedgridLayout = useMemo(() => {
|
const memoizedgridLayout = useMemo(() => {
|
||||||
// memoizing this component reduces the number of times it gets re-rendered to a minimum
|
// memoizing this component reduces the number of times it gets re-rendered to a minimum
|
||||||
return (
|
return (
|
||||||
|
@ -137,9 +209,7 @@ export const DashboardGrid = ({
|
||||||
gutterSize: useMargins ? DASHBOARD_MARGIN_SIZE : 0,
|
gutterSize: useMargins ? DASHBOARD_MARGIN_SIZE : 0,
|
||||||
rowHeight: DASHBOARD_GRID_HEIGHT,
|
rowHeight: DASHBOARD_GRID_HEIGHT,
|
||||||
columnCount: DASHBOARD_GRID_COLUMN_COUNT,
|
columnCount: DASHBOARD_GRID_COLUMN_COUNT,
|
||||||
keyboardDragTopLimit:
|
keyboardDragTopLimit: topOffset,
|
||||||
dashboardContainerRef?.current?.getBoundingClientRect().top ||
|
|
||||||
DEFAULT_DASHBOARD_DRAG_TOP_OFFSET,
|
|
||||||
}}
|
}}
|
||||||
useCustomDragHandle={true}
|
useCustomDragHandle={true}
|
||||||
renderPanelContents={renderPanelContents}
|
renderPanelContents={renderPanelContents}
|
||||||
|
@ -156,7 +226,7 @@ export const DashboardGrid = ({
|
||||||
onLayoutChange,
|
onLayoutChange,
|
||||||
expandedPanelId,
|
expandedPanelId,
|
||||||
viewMode,
|
viewMode,
|
||||||
dashboardContainerRef,
|
topOffset,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { dashboardClasses, dashboardStyles } = useMemo(() => {
|
const { dashboardClasses, dashboardStyles } = useMemo(() => {
|
||||||
|
@ -183,8 +253,18 @@ export const DashboardGrid = ({
|
||||||
}, [useMargins, viewMode, expandedPanelId, euiTheme.levels.toast]);
|
}, [useMargins, viewMode, expandedPanelId, euiTheme.levels.toast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={dashboardClasses} css={dashboardStyles}>
|
<div ref={layoutRef} className={dashboardClasses} css={dashboardStyles}>
|
||||||
{memoizedgridLayout}
|
{memoizedgridLayout}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const convertGridPanelToDashboardGridData = (panel: GridPanelData): GridData => {
|
||||||
|
return {
|
||||||
|
i: panel.id,
|
||||||
|
y: panel.row,
|
||||||
|
x: panel.column,
|
||||||
|
w: panel.width,
|
||||||
|
h: panel.height,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -50,7 +50,11 @@ export const useLayoutStyles = () => {
|
||||||
background-origin: content-box;
|
background-origin: content-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kbnGridPanel--dragPreview {
|
// styles for the area where the panel and/or section header will be dropped
|
||||||
|
.kbnGridPanel--dragPreview,
|
||||||
|
.kbnGridSection--dragPreview {
|
||||||
|
border-radius: ${euiTheme.border.radius.medium} ${euiTheme.border.radius.medium};
|
||||||
|
|
||||||
background-color: ${transparentize(euiTheme.colors.vis.euiColorVis0, 0.2)};
|
background-color: ${transparentize(euiTheme.colors.vis.euiColorVis0, 0.2)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +95,50 @@ export const useLayoutStyles = () => {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// styling for what the grid section header looks like when being dragged
|
||||||
|
.kbnGridSectionHeader--active {
|
||||||
|
background-color: ${euiTheme.colors.backgroundBasePlain};
|
||||||
|
outline: var(--dashboardActivePanelBorderStyle);
|
||||||
|
border-radius: ${euiTheme.border.radius.medium} ${euiTheme.border.radius.medium};
|
||||||
|
padding-left: 8px;
|
||||||
|
// hide accordian arrow + panel count text when row is being dragged
|
||||||
|
& .kbnGridSectionTitle--button svg,
|
||||||
|
& .kbnGridLayout--panelCount {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// styling for the section footer
|
||||||
|
.kbnGridSectionFooter {
|
||||||
|
height: ${euiTheme.size.s};
|
||||||
|
display: block;
|
||||||
|
border-top: ${euiTheme.border.thin};
|
||||||
|
// highlight the footer of a targeted section to make it clear where the section ends
|
||||||
|
&--targeted {
|
||||||
|
border-top: ${euiTheme.border.width.thick} solid
|
||||||
|
${transparentize(euiTheme.colors.vis.euiColorVis0, 0.5)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// hide footer border when section is being dragged
|
||||||
|
&:has(.kbnGridSectionHeader--active) .kbnGridSectionHeader--active + .kbnGridSectionFooter {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply a "fade out" effect when dragging a section header over another section, indicating that dropping is not allowed
|
||||||
|
.kbnGridSection--blocked {
|
||||||
|
z-index: 1;
|
||||||
|
background-color: ${transparentize(euiTheme.colors.backgroundBaseSubdued, 0.5)};
|
||||||
|
// the oulines of panels extend past 100% by 1px on each side, so adjust for that
|
||||||
|
margin-left: -1px;
|
||||||
|
margin-top: -1px;
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.kbnGridSection--blocked) .kbnGridSection--dragHandle {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}, [euiTheme]);
|
}, [euiTheme]);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
.dshDashboardViewport {
|
.dshDashboardViewport {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
&.dshDashboardViewport--empty {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&--panelExpanded {
|
&--panelExpanded {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,14 @@ import { EuiPortal } from '@elastic/eui';
|
||||||
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
||||||
import { ExitFullScreenButton } from '@kbn/shared-ux-button-exit-full-screen';
|
import { ExitFullScreenButton } from '@kbn/shared-ux-button-exit-full-screen';
|
||||||
|
|
||||||
import { ControlGroupApi } from '@kbn/controls-plugin/public';
|
|
||||||
import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';
|
import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';
|
||||||
|
import { ControlGroupApi } from '@kbn/controls-plugin/public';
|
||||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||||
import { DashboardGrid } from '../grid';
|
import { CONTROL_GROUP_EMBEDDABLE_ID } from '../../dashboard_api/control_group_manager';
|
||||||
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
import { useDashboardApi } from '../../dashboard_api/use_dashboard_api';
|
||||||
import { useDashboardInternalApi } from '../../dashboard_api/use_dashboard_internal_api';
|
import { useDashboardInternalApi } from '../../dashboard_api/use_dashboard_internal_api';
|
||||||
|
import { DashboardGrid } from '../grid';
|
||||||
import { DashboardEmptyScreen } from './empty_screen/dashboard_empty_screen';
|
import { DashboardEmptyScreen } from './empty_screen/dashboard_empty_screen';
|
||||||
import { CONTROL_GROUP_EMBEDDABLE_ID } from '../../dashboard_api/control_group_manager';
|
|
||||||
|
|
||||||
export const DashboardViewport = ({
|
export const DashboardViewport = ({
|
||||||
dashboardContainerRef,
|
dashboardContainerRef,
|
||||||
|
@ -54,12 +54,21 @@ export const DashboardViewport = ({
|
||||||
dashboardApi.setFullScreenMode(false);
|
dashboardApi.setFullScreenMode(false);
|
||||||
}, [dashboardApi]);
|
}, [dashboardApi]);
|
||||||
|
|
||||||
const panelCount = useMemo(() => {
|
const { panelCount, visiblePanelCount, sectionCount } = useMemo(() => {
|
||||||
return Object.keys(layout).length;
|
const panels = Object.values(layout.panels);
|
||||||
}, [layout]);
|
const visiblePanels = panels.filter(({ gridData }) => {
|
||||||
|
return !dashboardInternalApi.isSectionCollapsed(gridData.sectionId);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
panelCount: panels.length,
|
||||||
|
visiblePanelCount: visiblePanels.length,
|
||||||
|
sectionCount: Object.keys(layout.sections).length,
|
||||||
|
};
|
||||||
|
}, [layout, dashboardInternalApi]);
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
dshDashboardViewport: true,
|
dshDashboardViewport: true,
|
||||||
|
'dshDashboardViewport--empty': panelCount === 0 && sectionCount === 0,
|
||||||
'dshDashboardViewport--print': viewMode === 'print',
|
'dshDashboardViewport--print': viewMode === 'print',
|
||||||
'dshDashboardViewport--panelExpanded': Boolean(expandedPanelId),
|
'dshDashboardViewport--panelExpanded': Boolean(expandedPanelId),
|
||||||
});
|
});
|
||||||
|
@ -124,15 +133,18 @@ export const DashboardViewport = ({
|
||||||
<ExitFullScreenButton onExit={onExit} toggleChrome={!dashboardApi.isEmbeddedExternally} />
|
<ExitFullScreenButton onExit={onExit} toggleChrome={!dashboardApi.isEmbeddedExternally} />
|
||||||
</EuiPortal>
|
</EuiPortal>
|
||||||
)}
|
)}
|
||||||
{panelCount === 0 && <DashboardEmptyScreen />}
|
|
||||||
<div
|
<div
|
||||||
className={classes}
|
className={classes}
|
||||||
data-shared-items-container
|
data-shared-items-container
|
||||||
data-title={dashboardTitle}
|
data-title={dashboardTitle}
|
||||||
data-description={description}
|
data-description={description}
|
||||||
data-shared-items-count={panelCount}
|
data-shared-items-count={visiblePanelCount}
|
||||||
>
|
>
|
||||||
|
{panelCount === 0 && sectionCount === 0 ? (
|
||||||
|
<DashboardEmptyScreen />
|
||||||
|
) : (
|
||||||
<DashboardGrid dashboardContainerRef={dashboardContainerRef} />
|
<DashboardGrid dashboardContainerRef={dashboardContainerRef} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { BehaviorSubject } from 'rxjs';
|
||||||
import { DashboardStart } from './plugin';
|
import { DashboardStart } from './plugin';
|
||||||
import { DashboardState } from '../common/types';
|
import { DashboardState } from '../common/types';
|
||||||
import { getDashboardApi } from './dashboard_api/get_dashboard_api';
|
import { getDashboardApi } from './dashboard_api/get_dashboard_api';
|
||||||
import { DashboardPanelState } from '../common/dashboard_container/types';
|
import { DashboardPanelMap, DashboardSectionMap } from '../common';
|
||||||
|
|
||||||
export type Start = jest.Mocked<DashboardStart>;
|
export type Start = jest.Mocked<DashboardStart>;
|
||||||
|
|
||||||
|
@ -126,24 +126,67 @@ export function getSampleDashboardState(overrides?: Partial<DashboardState>): Da
|
||||||
timeRestore: false,
|
timeRestore: false,
|
||||||
viewMode: 'view',
|
viewMode: 'view',
|
||||||
panels: {},
|
panels: {},
|
||||||
|
sections: {},
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSampleDashboardPanel(
|
export function getMockDashboardPanels(
|
||||||
overrides: Partial<DashboardPanelState> & {
|
withSections: boolean = false,
|
||||||
explicitInput: { id: string };
|
overrides?: {
|
||||||
type: string;
|
panels?: DashboardPanelMap;
|
||||||
|
sections?: DashboardSectionMap;
|
||||||
}
|
}
|
||||||
): DashboardPanelState {
|
): { panels: DashboardPanelMap; sections: DashboardSectionMap } {
|
||||||
return {
|
const panels = {
|
||||||
gridData: {
|
'1': {
|
||||||
h: 15,
|
gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' },
|
||||||
w: 15,
|
type: 'lens',
|
||||||
x: 0,
|
explicitInput: { id: '1' },
|
||||||
y: 0,
|
|
||||||
i: overrides.explicitInput.id,
|
|
||||||
},
|
},
|
||||||
...overrides,
|
'2': {
|
||||||
|
gridData: { x: 6, y: 0, w: 6, h: 6, i: '2' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '2' },
|
||||||
|
},
|
||||||
|
...overrides?.panels,
|
||||||
};
|
};
|
||||||
|
if (!withSections) return { panels, sections: {} };
|
||||||
|
|
||||||
|
return {
|
||||||
|
panels: {
|
||||||
|
...panels,
|
||||||
|
'3': {
|
||||||
|
gridData: { x: 0, y: 0, w: 6, h: 6, i: '3', sectionId: 'section1' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '3' },
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
gridData: { x: 0, y: 0, w: 6, h: 6, i: '4', sectionId: 'section2' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '4' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
section1: {
|
||||||
|
id: 'section1',
|
||||||
|
title: 'Section One',
|
||||||
|
collapsed: true,
|
||||||
|
gridData: {
|
||||||
|
y: 6,
|
||||||
|
i: 'section1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
section2: {
|
||||||
|
id: 'section2',
|
||||||
|
title: 'Section Two',
|
||||||
|
collapsed: false,
|
||||||
|
gridData: {
|
||||||
|
y: 7,
|
||||||
|
i: 'section2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...overrides?.sections,
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getMockDashboardPanels } from '../mocks';
|
||||||
|
import { placeClonePanel } from './place_clone_panel_strategy';
|
||||||
|
|
||||||
|
describe('Clone panel placement strategies', () => {
|
||||||
|
it('no other panels', () => {
|
||||||
|
const currentPanels = {
|
||||||
|
'1': {
|
||||||
|
gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '1' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { newPanelPlacement, otherPanels } = placeClonePanel({
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
currentPanels,
|
||||||
|
placeBesideId: '1',
|
||||||
|
});
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 6, // placed right beside the other panel
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(currentPanels);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('panel collision at desired clone location', () => {
|
||||||
|
const { panels } = getMockDashboardPanels();
|
||||||
|
const { newPanelPlacement, otherPanels } = placeClonePanel({
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
currentPanels: panels,
|
||||||
|
placeBesideId: '1',
|
||||||
|
});
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 6, // instead of being placed beside the cloned panel, it is placed right below
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(panels);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores panels in other sections', () => {
|
||||||
|
const { panels } = getMockDashboardPanels(true);
|
||||||
|
|
||||||
|
const { newPanelPlacement, otherPanels } = placeClonePanel({
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
currentPanels: panels,
|
||||||
|
placeBesideId: '3',
|
||||||
|
sectionId: 'section1',
|
||||||
|
});
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 6, // placed beside panel 3, since is has space beside it in section1
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(panels);
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,10 +9,11 @@
|
||||||
|
|
||||||
import { PanelNotFoundError } from '@kbn/embeddable-plugin/public';
|
import { PanelNotFoundError } from '@kbn/embeddable-plugin/public';
|
||||||
import { cloneDeep, forOwn } from 'lodash';
|
import { cloneDeep, forOwn } from 'lodash';
|
||||||
|
|
||||||
import { DASHBOARD_GRID_COLUMN_COUNT } from '../../common/content_management';
|
import { DASHBOARD_GRID_COLUMN_COUNT } from '../../common/content_management';
|
||||||
import type { GridData } from '../../server/content_management';
|
import type { GridData } from '../../server/content_management';
|
||||||
import { DashboardLayoutItem } from '../dashboard_api/types';
|
|
||||||
import { PanelPlacementProps, PanelPlacementReturn } from './types';
|
import { PanelPlacementProps, PanelPlacementReturn } from './types';
|
||||||
|
import { DashboardPanel } from '../dashboard_api/types';
|
||||||
|
|
||||||
interface IplacementDirection {
|
interface IplacementDirection {
|
||||||
grid: Omit<GridData, 'i'>;
|
grid: Omit<GridData, 'i'>;
|
||||||
|
@ -42,6 +43,7 @@ function comparePanels(a: GridData, b: GridData): number {
|
||||||
export function placeClonePanel({
|
export function placeClonePanel({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
sectionId,
|
||||||
currentPanels,
|
currentPanels,
|
||||||
placeBesideId,
|
placeBesideId,
|
||||||
}: PanelPlacementProps & { placeBesideId: string }): PanelPlacementReturn {
|
}: PanelPlacementProps & { placeBesideId: string }): PanelPlacementReturn {
|
||||||
|
@ -51,8 +53,11 @@ export function placeClonePanel({
|
||||||
}
|
}
|
||||||
const beside = panelToPlaceBeside.gridData;
|
const beside = panelToPlaceBeside.gridData;
|
||||||
const otherPanelGridData: GridData[] = [];
|
const otherPanelGridData: GridData[] = [];
|
||||||
forOwn(currentPanels, (panel: DashboardLayoutItem, key: string | undefined) => {
|
forOwn(currentPanels, (panel: DashboardPanel) => {
|
||||||
|
if (panel.gridData.sectionId === sectionId) {
|
||||||
|
// only check against panels that are in the same section as the cloned panel
|
||||||
otherPanelGridData.push(panel.gridData);
|
otherPanelGridData.push(panel.gridData);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const possiblePlacementDirections: IplacementDirection[] = [
|
const possiblePlacementDirections: IplacementDirection[] = [
|
||||||
|
@ -109,8 +114,11 @@ export function placeClonePanel({
|
||||||
for (let j = position + 1; j < grid.length; j++) {
|
for (let j = position + 1; j < grid.length; j++) {
|
||||||
originalPositionInTheGrid = grid[j].i;
|
originalPositionInTheGrid = grid[j].i;
|
||||||
const { gridData, ...movedPanel } = cloneDeep(otherPanels[originalPositionInTheGrid]);
|
const { gridData, ...movedPanel } = cloneDeep(otherPanels[originalPositionInTheGrid]);
|
||||||
|
if (gridData.sectionId === sectionId) {
|
||||||
|
// only move panels in the cloned panel's section
|
||||||
const newGridData = { ...gridData, y: gridData.y + diff };
|
const newGridData = { ...gridData, y: gridData.y + diff };
|
||||||
otherPanels[originalPositionInTheGrid] = { ...movedPanel, gridData: newGridData };
|
otherPanels[originalPositionInTheGrid] = { ...movedPanel, gridData: newGridData };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return { newPanelPlacement: bottomPlacement.grid, otherPanels };
|
return { newPanelPlacement: bottomPlacement.grid, otherPanels };
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getMockDashboardPanels } from '../mocks';
|
||||||
|
import { PanelPlacementStrategy } from '../plugin_constants';
|
||||||
|
import { runPanelPlacementStrategy } from './place_new_panel_strategies';
|
||||||
|
|
||||||
|
describe('new panel placement strategies', () => {
|
||||||
|
describe('place at top', () => {
|
||||||
|
it('no other panels', () => {
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.placeAtTop,
|
||||||
|
{ width: 6, height: 6, currentPanels: {} }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('push other panels down', () => {
|
||||||
|
const { panels } = getMockDashboardPanels();
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.placeAtTop,
|
||||||
|
{ width: 6, height: 6, currentPanels: panels }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(
|
||||||
|
Object.keys(panels).reduce((prev, panelId) => {
|
||||||
|
const originalGridData = panels[panelId].gridData;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[panelId]: {
|
||||||
|
...panels[panelId],
|
||||||
|
gridData: {
|
||||||
|
...originalGridData,
|
||||||
|
y: originalGridData.y + 6, // panel was pushed down by height of new panel
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores panels in other sections', () => {
|
||||||
|
const { panels } = getMockDashboardPanels(true);
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.placeAtTop,
|
||||||
|
{ width: 6, height: 6, currentPanels: panels, sectionId: 'section1' }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(
|
||||||
|
Object.keys(panels).reduce((prev, panelId) => {
|
||||||
|
const originalGridData = panels[panelId].gridData;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[panelId]: {
|
||||||
|
...panels[panelId],
|
||||||
|
gridData: {
|
||||||
|
...originalGridData,
|
||||||
|
// only panels in the targetted section should get pushed down
|
||||||
|
...(originalGridData.sectionId === 'section1' && {
|
||||||
|
y: originalGridData.y + 6,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Find top left most open space', () => {
|
||||||
|
it('no other panels', () => {
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.findTopLeftMostOpenSpace,
|
||||||
|
{ width: 6, height: 6, currentPanels: {} }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('top left most space is available', () => {
|
||||||
|
const { panels } = getMockDashboardPanels(false, {
|
||||||
|
panels: {
|
||||||
|
'1': {
|
||||||
|
gridData: { x: 6, y: 0, w: 6, h: 6, i: '1' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '1' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.findTopLeftMostOpenSpace,
|
||||||
|
{ width: 6, height: 6, currentPanels: panels }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0, // placed in the first available spot
|
||||||
|
y: 0,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(panels); // other panels don't move with this strategy
|
||||||
|
});
|
||||||
|
|
||||||
|
it('panel must be pushed down', () => {
|
||||||
|
const { panels } = getMockDashboardPanels(true, {
|
||||||
|
panels: {
|
||||||
|
'5': {
|
||||||
|
gridData: { x: 6, y: 0, w: 42, h: 6, i: '5' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '1' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.findTopLeftMostOpenSpace,
|
||||||
|
{ width: 6, height: 6, currentPanels: panels }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 6,
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(panels); // other panels don't move with this strategy
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores panels in other sections', () => {
|
||||||
|
const { panels } = getMockDashboardPanels(true, {
|
||||||
|
panels: {
|
||||||
|
'1': {
|
||||||
|
gridData: { x: 0, y: 0, w: 6, h: 100, i: '1' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '1' },
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
gridData: { x: 6, y: 6, w: 42, h: 100, i: '2' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '2' },
|
||||||
|
},
|
||||||
|
'6': {
|
||||||
|
gridData: { x: 0, y: 6, w: 6, h: 6, i: '6', sectionId: 'section1' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '1' },
|
||||||
|
},
|
||||||
|
'7': {
|
||||||
|
gridData: { x: 6, y: 0, w: 42, h: 12, i: '7', sectionId: 'section1' },
|
||||||
|
type: 'lens',
|
||||||
|
explicitInput: { id: '1' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { newPanelPlacement, otherPanels } = runPanelPlacementStrategy(
|
||||||
|
PanelPlacementStrategy.findTopLeftMostOpenSpace,
|
||||||
|
{ width: 6, height: 6, currentPanels: panels, sectionId: 'section1' }
|
||||||
|
);
|
||||||
|
expect(newPanelPlacement).toEqual({
|
||||||
|
x: 0,
|
||||||
|
y: 12, // maxY is 12 for section1; maxY of 100 in section 0 is ignored
|
||||||
|
w: 6,
|
||||||
|
h: 6,
|
||||||
|
});
|
||||||
|
expect(otherPanels).toEqual(panels); // other panels don't move with this strategy
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,16 +15,19 @@ import { PanelPlacementProps, PanelPlacementReturn } from './types';
|
||||||
|
|
||||||
export const runPanelPlacementStrategy = (
|
export const runPanelPlacementStrategy = (
|
||||||
strategy: PanelPlacementStrategy,
|
strategy: PanelPlacementStrategy,
|
||||||
{ width, height, currentPanels }: PanelPlacementProps
|
{ width, height, currentPanels, sectionId }: PanelPlacementProps
|
||||||
): PanelPlacementReturn => {
|
): PanelPlacementReturn => {
|
||||||
switch (strategy) {
|
switch (strategy) {
|
||||||
case PanelPlacementStrategy.placeAtTop:
|
case PanelPlacementStrategy.placeAtTop:
|
||||||
const otherPanels = { ...currentPanels };
|
const otherPanels = { ...currentPanels };
|
||||||
for (const [id, panel] of Object.entries(currentPanels)) {
|
for (const [id, panel] of Object.entries(currentPanels)) {
|
||||||
|
// only consider collisions with panels in the same section
|
||||||
|
if (!sectionId || panel.gridData.sectionId === sectionId) {
|
||||||
const { gridData, ...currentPanel } = cloneDeep(panel);
|
const { gridData, ...currentPanel } = cloneDeep(panel);
|
||||||
const newGridData = { ...gridData, y: gridData.y + height };
|
const newGridData = { ...gridData, y: gridData.y + height };
|
||||||
otherPanels[id] = { ...currentPanel, gridData: newGridData };
|
otherPanels[id] = { ...currentPanel, gridData: newGridData };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
newPanelPlacement: { x: 0, y: 0, w: width, h: height },
|
newPanelPlacement: { x: 0, y: 0, w: width, h: height },
|
||||||
otherPanels,
|
otherPanels,
|
||||||
|
@ -35,7 +38,10 @@ export const runPanelPlacementStrategy = (
|
||||||
|
|
||||||
const currentPanelsArray = Object.values(currentPanels);
|
const currentPanelsArray = Object.values(currentPanels);
|
||||||
currentPanelsArray.forEach((panel) => {
|
currentPanelsArray.forEach((panel) => {
|
||||||
|
// only consider panels in the same section when calculating maxY
|
||||||
|
if (panel.gridData.sectionId === sectionId) {
|
||||||
maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY);
|
maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle case of empty grid.
|
// Handle case of empty grid.
|
||||||
|
@ -52,6 +58,7 @@ export const runPanelPlacementStrategy = (
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPanelsArray.forEach((panel) => {
|
currentPanelsArray.forEach((panel) => {
|
||||||
|
if (panel.gridData.sectionId === sectionId) {
|
||||||
for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) {
|
for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) {
|
||||||
for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) {
|
for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) {
|
||||||
const row = grid[y];
|
const row = grid[y];
|
||||||
|
@ -65,6 +72,7 @@ export const runPanelPlacementStrategy = (
|
||||||
grid[y][x] = 1;
|
grid[y][x] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let y = 0; y < maxY; y++) {
|
for (let y = 0; y < maxY; y++) {
|
||||||
|
|
|
@ -21,13 +21,14 @@ export interface PanelPlacementSettings {
|
||||||
|
|
||||||
export interface PanelPlacementReturn {
|
export interface PanelPlacementReturn {
|
||||||
newPanelPlacement: Omit<GridData, 'i'>;
|
newPanelPlacement: Omit<GridData, 'i'>;
|
||||||
otherPanels: DashboardLayout;
|
otherPanels: DashboardLayout['panels'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanelPlacementProps {
|
export interface PanelPlacementProps {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
currentPanels: DashboardLayout;
|
currentPanels: DashboardLayout['panels'];
|
||||||
|
sectionId?: string; // section where panel is being placed
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetPanelPlacementSettings<SerializedState extends object = object> = (
|
export type GetPanelPlacementSettings<SerializedState extends object = object> = (
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { has } from 'lodash';
|
|
||||||
import { injectSearchSourceReferences } from '@kbn/data-plugin/public';
|
import { injectSearchSourceReferences } from '@kbn/data-plugin/public';
|
||||||
import { Filter, Query } from '@kbn/es-query';
|
import { Filter, Query } from '@kbn/es-query';
|
||||||
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public';
|
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
import { has } from 'lodash';
|
||||||
|
|
||||||
import { cleanFiltersForSerialize } from '../../../utils/clean_filters_for_serialize';
|
|
||||||
import { getDashboardContentManagementCache } from '..';
|
import { getDashboardContentManagementCache } from '..';
|
||||||
import { convertPanelsArrayToPanelMap } from '../../../../common/lib/dashboard_panel_converters';
|
|
||||||
import { injectReferences } from '../../../../common/dashboard_saved_object/persistable_state/dashboard_saved_object_references';
|
import { injectReferences } from '../../../../common/dashboard_saved_object/persistable_state/dashboard_saved_object_references';
|
||||||
|
import { convertPanelsArrayToPanelSectionMaps } from '../../../../common/lib/dashboard_panel_converters';
|
||||||
import type { DashboardGetIn, DashboardGetOut } from '../../../../server/content_management';
|
import type { DashboardGetIn, DashboardGetOut } from '../../../../server/content_management';
|
||||||
import { DASHBOARD_CONTENT_ID } from '../../../utils/telemetry_constants';
|
|
||||||
import { DEFAULT_DASHBOARD_STATE } from '../../../dashboard_api/default_dashboard_state';
|
import { DEFAULT_DASHBOARD_STATE } from '../../../dashboard_api/default_dashboard_state';
|
||||||
|
import { cleanFiltersForSerialize } from '../../../utils/clean_filters_for_serialize';
|
||||||
|
import { DASHBOARD_CONTENT_ID } from '../../../utils/telemetry_constants';
|
||||||
import {
|
import {
|
||||||
contentManagementService,
|
contentManagementService,
|
||||||
dataService,
|
dataService,
|
||||||
|
@ -73,6 +73,7 @@ export const loadDashboardState = async ({
|
||||||
let resolveMeta: DashboardGetOut['meta'];
|
let resolveMeta: DashboardGetOut['meta'];
|
||||||
|
|
||||||
const cachedDashboard = dashboardContentManagementCache.fetchDashboard(id);
|
const cachedDashboard = dashboardContentManagementCache.fetchDashboard(id);
|
||||||
|
|
||||||
if (cachedDashboard) {
|
if (cachedDashboard) {
|
||||||
/** If the dashboard exists in the cache, use the cached version to load the dashboard */
|
/** If the dashboard exists in the cache, use the cached version to load the dashboard */
|
||||||
({ item: rawDashboardContent, meta: resolveMeta } = cachedDashboard);
|
({ item: rawDashboardContent, meta: resolveMeta } = cachedDashboard);
|
||||||
|
@ -149,7 +150,6 @@ export const loadDashboardState = async ({
|
||||||
const query = migrateLegacyQuery(
|
const query = migrateLegacyQuery(
|
||||||
searchSource?.getOwnField('query') || queryString.getDefaultQuery() // TODO SAVED DASHBOARDS determine if migrateLegacyQuery is still needed
|
searchSource?.getOwnField('query') || queryString.getDefaultQuery() // TODO SAVED DASHBOARDS determine if migrateLegacyQuery is still needed
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
description,
|
description,
|
||||||
|
@ -170,7 +170,9 @@ export const loadDashboardState = async ({
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const panelMap = convertPanelsArrayToPanelMap(panels ?? []);
|
const { panels: panelMap, sections: sectionsMap } = convertPanelsArrayToPanelSectionMaps(
|
||||||
|
panels ?? []
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
managed,
|
managed,
|
||||||
|
@ -187,6 +189,7 @@ export const loadDashboardState = async ({
|
||||||
panels: panelMap,
|
panels: panelMap,
|
||||||
query,
|
query,
|
||||||
title,
|
title,
|
||||||
|
sections: sectionsMap,
|
||||||
|
|
||||||
viewMode: 'view', // dashboards loaded from saved object default to view mode. If it was edited recently, the view mode from session storage will override this.
|
viewMode: 'view', // dashboards loaded from saved object default to view mode. If it was edited recently, the view mode from session storage will override this.
|
||||||
tags:
|
tags:
|
||||||
|
|
|
@ -11,6 +11,7 @@ export type {
|
||||||
ControlGroupAttributes,
|
ControlGroupAttributes,
|
||||||
GridData,
|
GridData,
|
||||||
DashboardPanel,
|
DashboardPanel,
|
||||||
|
DashboardSection,
|
||||||
DashboardAttributes,
|
DashboardAttributes,
|
||||||
DashboardItem,
|
DashboardItem,
|
||||||
DashboardGetIn,
|
DashboardGetIn,
|
||||||
|
|
|
@ -229,7 +229,16 @@ const searchSourceSchema = schema.object(
|
||||||
{ defaultValue: {}, unknowns: 'allow' }
|
{ defaultValue: {}, unknowns: 'allow' }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const gridDataSchema = schema.object({
|
const sectionGridDataSchema = schema.object({
|
||||||
|
y: schema.number({ meta: { description: 'The y coordinate of the section in grid units' } }),
|
||||||
|
i: schema.maybe(
|
||||||
|
schema.string({
|
||||||
|
meta: { description: 'The unique identifier of the section' },
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const panelGridDataSchema = schema.object({
|
||||||
x: schema.number({ meta: { description: 'The x coordinate of the panel in grid units' } }),
|
x: schema.number({ meta: { description: 'The x coordinate of the panel in grid units' } }),
|
||||||
y: schema.number({ meta: { description: 'The y coordinate of the panel in grid units' } }),
|
y: schema.number({ meta: { description: 'The y coordinate of the panel in grid units' } }),
|
||||||
w: schema.number({
|
w: schema.number({
|
||||||
|
@ -284,7 +293,7 @@ export const panelSchema = schema.object({
|
||||||
),
|
),
|
||||||
type: schema.string({ meta: { description: 'The embeddable type' } }),
|
type: schema.string({ meta: { description: 'The embeddable type' } }),
|
||||||
panelRefName: schema.maybe(schema.string()),
|
panelRefName: schema.maybe(schema.string()),
|
||||||
gridData: gridDataSchema,
|
gridData: panelGridDataSchema,
|
||||||
panelIndex: schema.maybe(
|
panelIndex: schema.maybe(
|
||||||
schema.string({
|
schema.string({
|
||||||
meta: { description: 'The unique ID of the panel.' },
|
meta: { description: 'The unique ID of the panel.' },
|
||||||
|
@ -302,6 +311,23 @@ export const panelSchema = schema.object({
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const sectionSchema = schema.object({
|
||||||
|
title: schema.string({
|
||||||
|
meta: { description: 'The title of the section.' },
|
||||||
|
}),
|
||||||
|
collapsed: schema.maybe(
|
||||||
|
schema.boolean({
|
||||||
|
meta: { description: 'The collapsed state of the section.' },
|
||||||
|
defaultValue: false,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
gridData: sectionGridDataSchema,
|
||||||
|
panels: schema.arrayOf(panelSchema, {
|
||||||
|
meta: { description: 'The panels that belong to the section.' },
|
||||||
|
defaultValue: [],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const optionsSchema = schema.object({
|
export const optionsSchema = schema.object({
|
||||||
hidePanelTitles: schema.boolean({
|
hidePanelTitles: schema.boolean({
|
||||||
defaultValue: DEFAULT_DASHBOARD_OPTIONS.hidePanelTitles,
|
defaultValue: DEFAULT_DASHBOARD_OPTIONS.hidePanelTitles,
|
||||||
|
@ -402,7 +428,7 @@ export const dashboardAttributesSchema = searchResultsAttributesSchema.extends({
|
||||||
|
|
||||||
// Dashboard Content
|
// Dashboard Content
|
||||||
controlGroupInput: schema.maybe(controlGroupInputSchema),
|
controlGroupInput: schema.maybe(controlGroupInputSchema),
|
||||||
panels: schema.arrayOf(panelSchema, { defaultValue: [] }),
|
panels: schema.arrayOf(schema.oneOf([panelSchema, sectionSchema]), { defaultValue: [] }),
|
||||||
options: optionsSchema,
|
options: optionsSchema,
|
||||||
version: schema.maybe(schema.number({ meta: { deprecated: true } })),
|
version: schema.maybe(schema.number({ meta: { deprecated: true } })),
|
||||||
});
|
});
|
||||||
|
@ -417,14 +443,29 @@ export const referenceSchema = schema.object(
|
||||||
);
|
);
|
||||||
|
|
||||||
const dashboardAttributesSchemaResponse = dashboardAttributesSchema.extends({
|
const dashboardAttributesSchemaResponse = dashboardAttributesSchema.extends({
|
||||||
|
// Responses always include the panel index (for panels) and gridData.i (for panels + sections)
|
||||||
panels: schema.arrayOf(
|
panels: schema.arrayOf(
|
||||||
|
schema.oneOf([
|
||||||
panelSchema.extends({
|
panelSchema.extends({
|
||||||
// Responses always include the panel index and gridData.i
|
|
||||||
panelIndex: schema.string(),
|
panelIndex: schema.string(),
|
||||||
gridData: gridDataSchema.extends({
|
gridData: panelGridDataSchema.extends({
|
||||||
i: schema.string(),
|
i: schema.string(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
sectionSchema.extends({
|
||||||
|
gridData: sectionGridDataSchema.extends({
|
||||||
|
i: schema.string(),
|
||||||
|
}),
|
||||||
|
panels: schema.arrayOf(
|
||||||
|
panelSchema.extends({
|
||||||
|
panelIndex: schema.string(),
|
||||||
|
gridData: panelGridDataSchema.extends({
|
||||||
|
i: schema.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
{ defaultValue: [] }
|
{ defaultValue: [] }
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ export type {
|
||||||
ControlGroupAttributes,
|
ControlGroupAttributes,
|
||||||
GridData,
|
GridData,
|
||||||
DashboardPanel,
|
DashboardPanel,
|
||||||
|
DashboardSection,
|
||||||
DashboardAttributes,
|
DashboardAttributes,
|
||||||
DashboardItem,
|
DashboardItem,
|
||||||
DashboardGetIn,
|
DashboardGetIn,
|
||||||
|
|
|
@ -10,21 +10,11 @@
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-api-server';
|
import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-api-server';
|
||||||
import type {
|
|
||||||
DashboardAttributes,
|
|
||||||
DashboardGetOut,
|
|
||||||
DashboardItem,
|
|
||||||
ItemAttrsToSavedObjectParams,
|
|
||||||
ItemAttrsToSavedObjectReturn,
|
|
||||||
ItemAttrsToSavedObjectWithTagsParams,
|
|
||||||
PartialDashboardItem,
|
|
||||||
SavedObjectToItemReturn,
|
|
||||||
} from './types';
|
|
||||||
import type { DashboardSavedObjectAttributes } from '../../dashboard_saved_object';
|
|
||||||
import type {
|
import type {
|
||||||
ControlGroupAttributes as ControlGroupAttributesV2,
|
ControlGroupAttributes as ControlGroupAttributesV2,
|
||||||
DashboardCrudTypes as DashboardCrudTypesV2,
|
DashboardCrudTypes as DashboardCrudTypesV2,
|
||||||
} from '../../../common/content_management/v2';
|
} from '../../../common/content_management/v2';
|
||||||
|
import type { DashboardSavedObjectAttributes } from '../../dashboard_saved_object';
|
||||||
import {
|
import {
|
||||||
transformControlGroupIn,
|
transformControlGroupIn,
|
||||||
transformControlGroupOut,
|
transformControlGroupOut,
|
||||||
|
@ -34,6 +24,16 @@ import {
|
||||||
transformSearchSourceIn,
|
transformSearchSourceIn,
|
||||||
transformSearchSourceOut,
|
transformSearchSourceOut,
|
||||||
} from './transforms';
|
} from './transforms';
|
||||||
|
import type {
|
||||||
|
DashboardAttributes,
|
||||||
|
DashboardGetOut,
|
||||||
|
DashboardItem,
|
||||||
|
ItemAttrsToSavedObjectParams,
|
||||||
|
ItemAttrsToSavedObjectReturn,
|
||||||
|
ItemAttrsToSavedObjectWithTagsParams,
|
||||||
|
PartialDashboardItem,
|
||||||
|
SavedObjectToItemReturn,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export function dashboardAttributesOut(
|
export function dashboardAttributesOut(
|
||||||
attributes: DashboardSavedObjectAttributes | Partial<DashboardSavedObjectAttributes>,
|
attributes: DashboardSavedObjectAttributes | Partial<DashboardSavedObjectAttributes>,
|
||||||
|
@ -46,6 +46,7 @@ export function dashboardAttributesOut(
|
||||||
kibanaSavedObjectMeta,
|
kibanaSavedObjectMeta,
|
||||||
optionsJSON,
|
optionsJSON,
|
||||||
panelsJSON,
|
panelsJSON,
|
||||||
|
sections,
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
timeFrom,
|
timeFrom,
|
||||||
timeRestore,
|
timeRestore,
|
||||||
|
@ -53,7 +54,6 @@ export function dashboardAttributesOut(
|
||||||
title,
|
title,
|
||||||
version,
|
version,
|
||||||
} = attributes;
|
} = attributes;
|
||||||
|
|
||||||
// Inject any tag names from references into the attributes
|
// Inject any tag names from references into the attributes
|
||||||
let tags: string[] | undefined;
|
let tags: string[] | undefined;
|
||||||
if (getTagNamesFromReferences && references && references.length) {
|
if (getTagNamesFromReferences && references && references.length) {
|
||||||
|
@ -68,7 +68,7 @@ export function dashboardAttributesOut(
|
||||||
kibanaSavedObjectMeta: transformSearchSourceOut(kibanaSavedObjectMeta),
|
kibanaSavedObjectMeta: transformSearchSourceOut(kibanaSavedObjectMeta),
|
||||||
}),
|
}),
|
||||||
...(optionsJSON && { options: transformOptionsOut(optionsJSON) }),
|
...(optionsJSON && { options: transformOptionsOut(optionsJSON) }),
|
||||||
...(panelsJSON && { panels: transformPanelsOut(panelsJSON) }),
|
...((panelsJSON || sections) && { panels: transformPanelsOut(panelsJSON, sections) }),
|
||||||
...(refreshInterval && {
|
...(refreshInterval && {
|
||||||
refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value },
|
refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value },
|
||||||
}),
|
}),
|
||||||
|
@ -107,7 +107,7 @@ export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2['
|
||||||
kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta),
|
kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta),
|
||||||
}),
|
}),
|
||||||
...(options && { optionsJSON: JSON.stringify(options) }),
|
...(options && { optionsJSON: JSON.stringify(options) }),
|
||||||
panelsJSON: panels ? transformPanelsIn(panels) : '[]',
|
panelsJSON: panels ? transformPanelsIn(panels, true).panelsJSON : '[]',
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
...(timeFrom && { timeFrom }),
|
...(timeFrom && { timeFrom }),
|
||||||
timeRestore,
|
timeRestore,
|
||||||
|
@ -130,6 +130,8 @@ export const itemAttrsToSavedObject = ({
|
||||||
}: ItemAttrsToSavedObjectParams): ItemAttrsToSavedObjectReturn => {
|
}: ItemAttrsToSavedObjectParams): ItemAttrsToSavedObjectReturn => {
|
||||||
try {
|
try {
|
||||||
const { controlGroupInput, kibanaSavedObjectMeta, options, panels, tags, ...rest } = attributes;
|
const { controlGroupInput, kibanaSavedObjectMeta, options, panels, tags, ...rest } = attributes;
|
||||||
|
const { panelsJSON, sections } = transformPanelsIn(panels);
|
||||||
|
|
||||||
const soAttributes = {
|
const soAttributes = {
|
||||||
...rest,
|
...rest,
|
||||||
...(controlGroupInput && {
|
...(controlGroupInput && {
|
||||||
|
@ -139,8 +141,9 @@ export const itemAttrsToSavedObject = ({
|
||||||
optionsJSON: JSON.stringify(options),
|
optionsJSON: JSON.stringify(options),
|
||||||
}),
|
}),
|
||||||
...(panels && {
|
...(panels && {
|
||||||
panelsJSON: transformPanelsIn(panels),
|
panelsJSON,
|
||||||
}),
|
}),
|
||||||
|
...(sections?.length && { sections }),
|
||||||
...(kibanaSavedObjectMeta && {
|
...(kibanaSavedObjectMeta && {
|
||||||
kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta),
|
kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta),
|
||||||
}),
|
}),
|
||||||
|
@ -217,7 +220,6 @@ export function savedObjectToItem(
|
||||||
version,
|
version,
|
||||||
managed,
|
managed,
|
||||||
} = savedObject;
|
} = savedObject;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const attributesOut = allowedAttributes
|
const attributesOut = allowedAttributes
|
||||||
? pick(
|
? pick(
|
||||||
|
|
|
@ -30,7 +30,7 @@ describe('transformPanelsIn', () => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const result = transformPanelsIn(panels as DashboardPanel[]);
|
const result = transformPanelsIn(panels as DashboardPanel[]);
|
||||||
expect(result).toEqual(
|
expect(result.panelsJSON).toEqual(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{
|
{
|
||||||
type: 'foo',
|
type: 'foo',
|
||||||
|
|
|
@ -9,13 +9,46 @@
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { DashboardSavedObjectAttributes } from '../../../../dashboard_saved_object';
|
import { isDashboardSection } from '../../../../../common/lib/dashboard_panel_converters';
|
||||||
import { DashboardAttributes } from '../../types';
|
import {
|
||||||
|
DashboardSavedObjectAttributes,
|
||||||
|
SavedDashboardPanel,
|
||||||
|
SavedDashboardSection,
|
||||||
|
} from '../../../../dashboard_saved_object';
|
||||||
|
import { DashboardAttributes, DashboardPanel, DashboardSection } from '../../types';
|
||||||
|
|
||||||
export function transformPanelsIn(
|
export function transformPanelsIn(
|
||||||
panels: DashboardAttributes['panels']
|
widgets: DashboardAttributes['panels'] | undefined,
|
||||||
): DashboardSavedObjectAttributes['panelsJSON'] {
|
dropSections: boolean = false
|
||||||
const updatedPanels = panels.map(({ panelIndex, gridData, panelConfig, ...restPanel }) => {
|
): {
|
||||||
|
panelsJSON: DashboardSavedObjectAttributes['panelsJSON'];
|
||||||
|
sections: DashboardSavedObjectAttributes['sections'];
|
||||||
|
} {
|
||||||
|
const panels: SavedDashboardPanel[] = [];
|
||||||
|
const sections: SavedDashboardSection[] = [];
|
||||||
|
|
||||||
|
widgets?.forEach((widget) => {
|
||||||
|
if (isDashboardSection(widget)) {
|
||||||
|
const { panels: sectionPanels, gridData, ...restOfSection } = widget as DashboardSection;
|
||||||
|
const idx = gridData.i ?? uuidv4();
|
||||||
|
sections.push({ ...restOfSection, gridData: { ...gridData, i: idx } });
|
||||||
|
(sectionPanels as DashboardPanel[]).forEach((panel) => {
|
||||||
|
const transformed = transformPanel(panel);
|
||||||
|
panels.push({
|
||||||
|
...transformed,
|
||||||
|
gridData: { ...transformed.gridData, ...(!dropSections && { sectionId: idx }) },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// widget is a panel
|
||||||
|
panels.push(transformPanel(widget));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { panelsJSON: JSON.stringify(panels), sections };
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformPanel(panel: DashboardPanel): SavedDashboardPanel {
|
||||||
|
const { panelIndex, gridData, panelConfig, ...restPanel } = panel as DashboardPanel;
|
||||||
const idx = panelIndex ?? uuidv4();
|
const idx = panelIndex ?? uuidv4();
|
||||||
return {
|
return {
|
||||||
...restPanel,
|
...restPanel,
|
||||||
|
@ -26,7 +59,4 @@ export function transformPanelsIn(
|
||||||
i: idx,
|
i: idx,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
return JSON.stringify(updatedPanels);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,25 +7,51 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { flow } from 'lodash';
|
import { SavedDashboardPanel, SavedDashboardSection } from '../../../../dashboard_saved_object';
|
||||||
import { SavedDashboardPanel } from '../../../../dashboard_saved_object';
|
import { DashboardAttributes, DashboardPanel, DashboardSection } from '../../types';
|
||||||
import { DashboardAttributes } from '../../types';
|
|
||||||
|
|
||||||
export function transformPanelsOut(panelsJSON: string): DashboardAttributes['panels'] {
|
export function transformPanelsOut(
|
||||||
return flow(JSON.parse, transformPanelsProperties)(panelsJSON);
|
panelsJSON: string = '{}',
|
||||||
|
sections: SavedDashboardSection[] = []
|
||||||
|
): DashboardAttributes['panels'] {
|
||||||
|
const panels = JSON.parse(panelsJSON);
|
||||||
|
const sectionsMap: { [uuid: string]: DashboardPanel | DashboardSection } = sections.reduce(
|
||||||
|
(prev, section) => {
|
||||||
|
const sectionId = section.gridData.i;
|
||||||
|
return { ...prev, [sectionId]: { ...section, panels: [] } };
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
panels.forEach((panel: SavedDashboardPanel) => {
|
||||||
|
const { sectionId } = panel.gridData;
|
||||||
|
if (sectionId) {
|
||||||
|
(sectionsMap[sectionId] as DashboardSection).panels.push(transformPanelProperties(panel));
|
||||||
|
} else {
|
||||||
|
sectionsMap[panel.panelIndex] = transformPanelProperties(panel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.values(sectionsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformPanelsProperties(panels: SavedDashboardPanel[]) {
|
function transformPanelProperties({
|
||||||
return panels.map(
|
embeddableConfig,
|
||||||
({ embeddableConfig, gridData, id, panelIndex, panelRefName, title, type, version }) => ({
|
|
||||||
gridData,
|
gridData,
|
||||||
id,
|
id,
|
||||||
|
panelIndex,
|
||||||
|
panelRefName,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
version,
|
||||||
|
}: SavedDashboardPanel) {
|
||||||
|
const { sectionId, ...rest } = gridData; // drop section ID, if it exists
|
||||||
|
return {
|
||||||
|
gridData: rest,
|
||||||
|
id,
|
||||||
panelConfig: embeddableConfig,
|
panelConfig: embeddableConfig,
|
||||||
panelIndex,
|
panelIndex,
|
||||||
panelRefName,
|
panelRefName,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
version,
|
version,
|
||||||
})
|
};
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ import { WithRequiredProperty } from '@kbn/utility-types';
|
||||||
import {
|
import {
|
||||||
dashboardItemSchema,
|
dashboardItemSchema,
|
||||||
controlGroupInputSchema,
|
controlGroupInputSchema,
|
||||||
gridDataSchema,
|
panelGridDataSchema,
|
||||||
panelSchema,
|
panelSchema,
|
||||||
|
sectionSchema,
|
||||||
dashboardAttributesSchema,
|
dashboardAttributesSchema,
|
||||||
dashboardCreateOptionsSchema,
|
dashboardCreateOptionsSchema,
|
||||||
dashboardCreateResultSchema,
|
dashboardCreateResultSchema,
|
||||||
|
@ -43,8 +44,9 @@ export type DashboardPanel = Omit<TypeOf<typeof panelSchema>, 'panelConfig'> & {
|
||||||
panelConfig: TypeOf<typeof panelSchema>['panelConfig'] & { [key: string]: any };
|
panelConfig: TypeOf<typeof panelSchema>['panelConfig'] & { [key: string]: any };
|
||||||
gridData: GridData;
|
gridData: GridData;
|
||||||
};
|
};
|
||||||
|
export type DashboardSection = TypeOf<typeof sectionSchema>;
|
||||||
export type DashboardAttributes = Omit<TypeOf<typeof dashboardAttributesSchema>, 'panels'> & {
|
export type DashboardAttributes = Omit<TypeOf<typeof dashboardAttributesSchema>, 'panels'> & {
|
||||||
panels: DashboardPanel[];
|
panels: Array<DashboardPanel | DashboardSection>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DashboardItem = TypeOf<typeof dashboardItemSchema>;
|
export type DashboardItem = TypeOf<typeof dashboardItemSchema>;
|
||||||
|
@ -54,7 +56,7 @@ export type PartialDashboardItem = Omit<DashboardItem, 'attributes' | 'reference
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ControlGroupAttributes = TypeOf<typeof controlGroupInputSchema>;
|
export type ControlGroupAttributes = TypeOf<typeof controlGroupInputSchema>;
|
||||||
export type GridData = WithRequiredProperty<TypeOf<typeof gridDataSchema>, 'i'>;
|
export type GridData = WithRequiredProperty<TypeOf<typeof panelGridDataSchema>, 'i'>;
|
||||||
|
|
||||||
export type DashboardGetIn = GetIn<typeof CONTENT_ID>;
|
export type DashboardGetIn = GetIn<typeof CONTENT_ID>;
|
||||||
export type DashboardGetOut = TypeOf<typeof dashboardGetResultSchema>;
|
export type DashboardGetOut = TypeOf<typeof dashboardGetResultSchema>;
|
||||||
|
|
|
@ -79,6 +79,10 @@ export const createDashboardSavedObjectType = ({
|
||||||
},
|
},
|
||||||
optionsJSON: { type: 'text', index: false },
|
optionsJSON: { type: 'text', index: false },
|
||||||
panelsJSON: { type: 'text', index: false },
|
panelsJSON: { type: 'text', index: false },
|
||||||
|
sections: {
|
||||||
|
properties: {},
|
||||||
|
dynamic: false,
|
||||||
|
},
|
||||||
refreshInterval: {
|
refreshInterval: {
|
||||||
properties: {
|
properties: {
|
||||||
display: { type: 'keyword', index: false, doc_values: false },
|
display: { type: 'keyword', index: false, doc_values: false },
|
||||||
|
|
|
@ -11,4 +11,9 @@ export {
|
||||||
createDashboardSavedObjectType,
|
createDashboardSavedObjectType,
|
||||||
DASHBOARD_SAVED_OBJECT_TYPE,
|
DASHBOARD_SAVED_OBJECT_TYPE,
|
||||||
} from './dashboard_saved_object';
|
} from './dashboard_saved_object';
|
||||||
export type { DashboardSavedObjectAttributes, GridData, SavedDashboardPanel } from './schema';
|
export type {
|
||||||
|
DashboardSavedObjectAttributes,
|
||||||
|
GridData,
|
||||||
|
SavedDashboardPanel,
|
||||||
|
SavedDashboardSection,
|
||||||
|
} from './schema';
|
||||||
|
|
|
@ -7,5 +7,10 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type { DashboardSavedObjectAttributes, GridData, SavedDashboardPanel } from './latest';
|
export type {
|
||||||
|
DashboardSavedObjectAttributes,
|
||||||
|
GridData,
|
||||||
|
SavedDashboardPanel,
|
||||||
|
SavedDashboardSection,
|
||||||
|
} from './latest';
|
||||||
export { dashboardSavedObjectSchema } from './latest';
|
export { dashboardSavedObjectSchema } from './latest';
|
||||||
|
|
|
@ -13,4 +13,5 @@ export {
|
||||||
type DashboardAttributes as DashboardSavedObjectAttributes,
|
type DashboardAttributes as DashboardSavedObjectAttributes,
|
||||||
type GridData,
|
type GridData,
|
||||||
type SavedDashboardPanel,
|
type SavedDashboardPanel,
|
||||||
|
type SavedDashboardSection,
|
||||||
} from './v2';
|
} from './v2';
|
||||||
|
|
|
@ -7,5 +7,10 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type { DashboardAttributes, GridData, SavedDashboardPanel } from './types';
|
export type {
|
||||||
|
DashboardAttributes,
|
||||||
|
GridData,
|
||||||
|
SavedDashboardPanel,
|
||||||
|
SavedDashboardSection,
|
||||||
|
} from './types';
|
||||||
export { controlGroupInputSchema, dashboardAttributesSchema } from './v2';
|
export { controlGroupInputSchema, dashboardAttributesSchema } from './v2';
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import { Serializable } from '@kbn/utility-types';
|
import { Serializable } from '@kbn/utility-types';
|
||||||
import { TypeOf } from '@kbn/config-schema';
|
import { TypeOf } from '@kbn/config-schema';
|
||||||
import { dashboardAttributesSchema, gridDataSchema } from './v2';
|
import { dashboardAttributesSchema, gridDataSchema, sectionSchema } from './v2';
|
||||||
|
|
||||||
export type DashboardAttributes = TypeOf<typeof dashboardAttributesSchema>;
|
export type DashboardAttributes = TypeOf<typeof dashboardAttributesSchema>;
|
||||||
export type GridData = TypeOf<typeof gridDataSchema>;
|
export type GridData = TypeOf<typeof gridDataSchema>;
|
||||||
|
@ -33,3 +33,8 @@ export interface SavedDashboardPanel {
|
||||||
*/
|
*/
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A saved dashboard section parsed directly from the Dashboard Attributes
|
||||||
|
*/
|
||||||
|
export type SavedDashboardSection = TypeOf<typeof sectionSchema>;
|
||||||
|
|
|
@ -13,6 +13,26 @@ import {
|
||||||
dashboardAttributesSchema as dashboardAttributesSchemaV1,
|
dashboardAttributesSchema as dashboardAttributesSchemaV1,
|
||||||
} from '../v1';
|
} from '../v1';
|
||||||
|
|
||||||
|
// sections only include y + i for grid data
|
||||||
|
export const sectionGridDataSchema = schema.object({
|
||||||
|
y: schema.number(),
|
||||||
|
i: schema.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// panels include all grid data keys, including those that sections use
|
||||||
|
export const gridDataSchema = sectionGridDataSchema.extends({
|
||||||
|
x: schema.number(),
|
||||||
|
w: schema.number(),
|
||||||
|
h: schema.number(),
|
||||||
|
sectionId: schema.maybe(schema.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sectionSchema = schema.object({
|
||||||
|
title: schema.string(),
|
||||||
|
collapsed: schema.maybe(schema.boolean()),
|
||||||
|
gridData: sectionGridDataSchema,
|
||||||
|
});
|
||||||
|
|
||||||
export const controlGroupInputSchema = controlGroupInputSchemaV1.extends(
|
export const controlGroupInputSchema = controlGroupInputSchemaV1.extends(
|
||||||
{
|
{
|
||||||
showApplySelections: schema.maybe(schema.boolean()),
|
showApplySelections: schema.maybe(schema.boolean()),
|
||||||
|
@ -23,14 +43,7 @@ export const controlGroupInputSchema = controlGroupInputSchemaV1.extends(
|
||||||
export const dashboardAttributesSchema = dashboardAttributesSchemaV1.extends(
|
export const dashboardAttributesSchema = dashboardAttributesSchemaV1.extends(
|
||||||
{
|
{
|
||||||
controlGroupInput: schema.maybe(controlGroupInputSchema),
|
controlGroupInput: schema.maybe(controlGroupInputSchema),
|
||||||
|
sections: schema.maybe(schema.arrayOf(sectionSchema)),
|
||||||
},
|
},
|
||||||
{ unknowns: 'ignore' }
|
{ unknowns: 'ignore' }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const gridDataSchema = schema.object({
|
|
||||||
x: schema.number(),
|
|
||||||
y: schema.number(),
|
|
||||||
w: schema.number(),
|
|
||||||
h: schema.number(),
|
|
||||||
i: schema.string(),
|
|
||||||
});
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export async function plugin(initializerContext: PluginInitializerContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { DashboardPluginSetup, DashboardPluginStart } from './types';
|
export type { DashboardPluginSetup, DashboardPluginStart } from './types';
|
||||||
export type { DashboardAttributes, DashboardPanel } from './content_management';
|
export type { DashboardAttributes, DashboardPanel, DashboardSection } from './content_management';
|
||||||
export type { DashboardSavedObjectAttributes } from './dashboard_saved_object';
|
export type { DashboardSavedObjectAttributes } from './dashboard_saved_object';
|
||||||
|
|
||||||
export { PUBLIC_API_PATH } from './api/constants';
|
export { PUBLIC_API_PATH } from './api/constants';
|
||||||
|
|
|
@ -12,6 +12,7 @@ import chroma from 'chroma-js';
|
||||||
import rison from '@kbn/rison';
|
import rison from '@kbn/rison';
|
||||||
import { DEFAULT_PANEL_WIDTH } from '@kbn/dashboard-plugin/common/content_management/constants';
|
import { DEFAULT_PANEL_WIDTH } from '@kbn/dashboard-plugin/common/content_management/constants';
|
||||||
import { SharedDashboardState } from '@kbn/dashboard-plugin/common/types';
|
import { SharedDashboardState } from '@kbn/dashboard-plugin/common/types';
|
||||||
|
import { DashboardPanel } from '@kbn/dashboard-plugin/server';
|
||||||
import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../../page_objects/dashboard_page';
|
import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../../page_objects/dashboard_page';
|
||||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||||
|
|
||||||
|
@ -231,7 +232,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
(appState: Partial<SharedDashboardState>) => {
|
(appState: Partial<SharedDashboardState>) => {
|
||||||
log.debug(JSON.stringify(appState, null, ' '));
|
log.debug(JSON.stringify(appState, null, ' '));
|
||||||
return {
|
return {
|
||||||
panels: (appState.panels ?? []).map((panel) => {
|
panels: (appState.panels ?? []).map((widget) => {
|
||||||
|
const panel = widget as DashboardPanel;
|
||||||
return {
|
return {
|
||||||
...panel,
|
...panel,
|
||||||
gridData: {
|
gridData: {
|
||||||
|
@ -306,7 +308,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
currentUrl,
|
currentUrl,
|
||||||
(appState: Partial<SharedDashboardState>) => {
|
(appState: Partial<SharedDashboardState>) => {
|
||||||
return {
|
return {
|
||||||
panels: (appState.panels ?? []).map((panel) => {
|
panels: (appState.panels ?? []).map((widget) => {
|
||||||
|
const panel = widget as DashboardPanel;
|
||||||
return {
|
return {
|
||||||
...panel,
|
...panel,
|
||||||
panelConfig: {
|
panelConfig: {
|
||||||
|
@ -350,7 +353,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
currentUrl,
|
currentUrl,
|
||||||
(appState: Partial<SharedDashboardState>) => {
|
(appState: Partial<SharedDashboardState>) => {
|
||||||
return {
|
return {
|
||||||
panels: (appState.panels ?? []).map((panel) => {
|
panels: (appState.panels ?? []).map((widget) => {
|
||||||
|
const panel = widget as DashboardPanel;
|
||||||
return {
|
return {
|
||||||
...panel,
|
...panel,
|
||||||
panelConfig: {
|
panelConfig: {
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
|
// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
|
||||||
expect(panelTypes.length).to.eql(10);
|
expect(panelTypes.length).to.eql(11);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,19 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import type { SavedObjectsFindResult } from '@kbn/core/server';
|
|
||||||
import { IContentClient } from '@kbn/content-management-plugin/server/types';
|
import { IContentClient } from '@kbn/content-management-plugin/server/types';
|
||||||
|
import type { Logger, SavedObjectsFindResult } from '@kbn/core/server';
|
||||||
|
import { isDashboardSection } from '@kbn/dashboard-plugin/common';
|
||||||
|
import type { DashboardAttributes, DashboardPanel } from '@kbn/dashboard-plugin/server';
|
||||||
|
import type { LensAttributes } from '@kbn/lens-embeddable-utils';
|
||||||
import type {
|
import type {
|
||||||
FieldBasedIndexPatternColumn,
|
FieldBasedIndexPatternColumn,
|
||||||
GenericIndexPatternColumn,
|
GenericIndexPatternColumn,
|
||||||
} from '@kbn/lens-plugin/public';
|
} from '@kbn/lens-plugin/public';
|
||||||
import type { Logger } from '@kbn/core/server';
|
import type { RelatedDashboard, RelevantPanel } from '@kbn/observability-schema';
|
||||||
import type { LensAttributes } from '@kbn/lens-embeddable-utils';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import type { RelevantPanel, RelatedDashboard } from '@kbn/observability-schema';
|
|
||||||
import type { DashboardAttributes, DashboardPanel } from '@kbn/dashboard-plugin/server';
|
|
||||||
import type { InvestigateAlertsClient } from './investigate_alerts_client';
|
|
||||||
import type { AlertData } from './alert_data';
|
import type { AlertData } from './alert_data';
|
||||||
|
import type { InvestigateAlertsClient } from './investigate_alerts_client';
|
||||||
|
|
||||||
type Dashboard = SavedObjectsFindResult<DashboardAttributes>;
|
type Dashboard = SavedObjectsFindResult<DashboardAttributes>;
|
||||||
export class RelatedDashboardsClient {
|
export class RelatedDashboardsClient {
|
||||||
|
@ -177,19 +177,21 @@ export class RelatedDashboardsClient {
|
||||||
return { dashboards: relevantDashboards };
|
return { dashboards: relevantDashboards };
|
||||||
}
|
}
|
||||||
|
|
||||||
getPanelsByIndex(index: string, panels: DashboardPanel[]): DashboardPanel[] {
|
getPanelsByIndex(index: string, panels: DashboardAttributes['panels']): DashboardPanel[] {
|
||||||
const panelsByIndex = panels.filter((p) => {
|
const panelsByIndex = panels.filter((p) => {
|
||||||
|
if (isDashboardSection(p)) return false; // filter out sections
|
||||||
const panelIndices = this.getPanelIndices(p);
|
const panelIndices = this.getPanelIndices(p);
|
||||||
return panelIndices.has(index);
|
return panelIndices.has(index);
|
||||||
});
|
}) as DashboardPanel[]; // filtering with type guard doesn't actually limit type, so need to cast
|
||||||
return panelsByIndex;
|
return panelsByIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPanelsByField(
|
getPanelsByField(
|
||||||
fields: string[],
|
fields: string[],
|
||||||
panels: DashboardPanel[]
|
panels: DashboardAttributes['panels']
|
||||||
): Array<{ matchingFields: Set<string>; panel: DashboardPanel }> {
|
): Array<{ matchingFields: Set<string>; panel: DashboardPanel }> {
|
||||||
const panelsByField = panels.reduce((acc, p) => {
|
const panelsByField = panels.reduce((acc, p) => {
|
||||||
|
if (isDashboardSection(p)) return acc; // filter out sections
|
||||||
const panelFields = this.getPanelFields(p);
|
const panelFields = this.getPanelFields(p);
|
||||||
const matchingFields = fields.filter((f) => panelFields.has(f));
|
const matchingFields = fields.filter((f) => panelFields.has(f));
|
||||||
if (matchingFields.length) {
|
if (matchingFields.length) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
|
// Any changes to the number of panels needs to be audited by @elastic/kibana-presentation
|
||||||
expect(panelTypes.length).to.eql(20);
|
expect(panelTypes.length).to.eql(21);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue