diff --git a/internal/builder/dashboards.go b/internal/builder/dashboards.go index 24d533e8b3..5f616a752f 100644 --- a/internal/builder/dashboards.go +++ b/internal/builder/dashboards.go @@ -13,14 +13,21 @@ import ( "github.com/elastic/elastic-package/internal/common" ) +const ( + panelsAttribute = "attributes.panelsJSON" + embeddableConfigAttribute = "embeddableConfig" +) + var fieldsToEncode = []string{ + "attributes.controlGroupInput.ignoreParentSettingsJSON", + "attributes.controlGroupInput.panelsJSON", "attributes.kibanaSavedObjectMeta.searchSourceJSON", "attributes.layerListJSON", "attributes.mapStateJSON", "attributes.optionsJSON", - "attributes.panelsJSON", "attributes.uiStateJSON", "attributes.visState", + panelsAttribute, } func encodeDashboards(destinationDir string) error { @@ -29,14 +36,13 @@ func encodeDashboards(destinationDir string) error { return err } for _, file := range savedObjects { - data, err := os.ReadFile(file) if err != nil { return err } - output, changed, err := encodedSavedObject(data) + output, changed, err := encodeSavedObject(data) if err != nil { - return err + return fmt.Errorf("encoding %s: %w", file, err) } if changed { @@ -53,16 +59,28 @@ func encodeDashboards(destinationDir string) error { // which are stored in encoded JSON in Kibana. // The reason is that for versioning it is much nicer to have the full // json so only on packaging this is changed. -func encodedSavedObject(data []byte) ([]byte, bool, error) { +func encodeSavedObject(data []byte) ([]byte, bool, error) { savedObject := common.MapStr{} err := json.Unmarshal(data, &savedObject) if err != nil { return nil, false, fmt.Errorf("unmarshalling saved object failed: %w", err) } - var changed bool + object, changed, err := encodeObjectMapStr(savedObject) + if err != nil { + return nil, false, err + } + + return []byte(object.StringToPrint()), changed, nil +} + +func encodeObjectMapStr(object common.MapStr) (common.MapStr, bool, error) { + object, changed, err := encodeEmbeddedPanels(object) + if err != nil { + return nil, false, err + } for _, v := range fieldsToEncode { - out, err := savedObject.GetValue(v) + out, err := object.GetValue(v) // This means the key did not exists, no conversion needed. if err != nil { continue @@ -80,11 +98,59 @@ func encodedSavedObject(data []byte) ([]byte, bool, error) { if err != nil { return nil, false, err } - _, err = savedObject.Put(v, string(r)) + _, err = object.Put(v, string(r)) if err != nil { return nil, false, fmt.Errorf("can't put value to the saved object: %w", err) } changed = true } - return []byte(savedObject.StringToPrint()), changed, nil + return object, changed, nil +} + +func encodeEmbeddedPanels(object common.MapStr) (common.MapStr, bool, error) { + embeddedPanelsValue, err := object.GetValue(panelsAttribute) + if err == common.ErrKeyNotFound { + return object, false, nil + } + if err != nil { + return nil, false, fmt.Errorf("retrieving embedded panels failed: %w", err) + } + _, isEncoded := embeddedPanelsValue.(string) + if isEncoded { + // This is already encoded, probably exported with an old version of elastic-package, do nothing. + return object, false, nil + } + embeddedPanels, ok := embeddedPanelsValue.([]any) + if !ok { + return nil, false, fmt.Errorf("expected list of panels, found %T", embeddedPanelsValue) + } + + changed := false + for i, panelValue := range embeddedPanels { + panel, ok := panelValue.(map[string]any) + if !ok { + return nil, false, fmt.Errorf("expected panel in map format, found %T", panel) + } + embeddableConfigValue, ok := panel[embeddableConfigAttribute] + if !ok { + continue + } + embeddableConfig, ok := embeddableConfigValue.(map[string]any) + if !ok { + return nil, false, fmt.Errorf("embeddable config is not a map, found %T", embeddableConfigValue) + } + embeddableConfig, embeddableChanged, err := encodeObjectMapStr(common.MapStr(embeddableConfig)) + if err != nil { + return nil, false, fmt.Errorf("econding embedded object failed: %w", err) + } + if embeddableChanged { + changed = true + } + + panel[embeddableConfigAttribute] = embeddableConfig + embeddedPanels[i] = panel + } + object.Put(panelsAttribute, embeddedPanels) + + return object, changed, nil } diff --git a/internal/export/transform_decode.go b/internal/export/transform_decode.go index be66466772..f62af73166 100644 --- a/internal/export/transform_decode.go +++ b/internal/export/transform_decode.go @@ -11,18 +11,23 @@ import ( "github.com/elastic/elastic-package/internal/common" ) -var ( - encodedFields = []string{ - "attributes.kibanaSavedObjectMeta.searchSourceJSON", - "attributes.layerListJSON", - "attributes.mapStateJSON", - "attributes.optionsJSON", - "attributes.panelsJSON", - "attributes.uiStateJSON", - "attributes.visState", - } +const ( + panelsAttribute = "attributes.panelsJSON" + embeddableConfigAttribute = "embeddableConfig" ) +var encodedFields = []string{ + "attributes.controlGroupInput.ignoreParentSettingsJSON", + "attributes.controlGroupInput.panelsJSON", + "attributes.kibanaSavedObjectMeta.searchSourceJSON", + "attributes.layerListJSON", + "attributes.mapStateJSON", + "attributes.optionsJSON", + "attributes.uiStateJSON", + "attributes.visState", + panelsAttribute, +} + func decodeObject(ctx *transformationContext, object common.MapStr) (common.MapStr, error) { for _, fieldToDecode := range encodedFields { v, err := object.GetValue(fieldToDecode) @@ -51,5 +56,44 @@ func decodeObject(ctx *transformationContext, object common.MapStr) (common.MapS return nil, fmt.Errorf("can't update field (key: %s): %w", fieldToDecode, err) } } + + object, err := decodeEmbeddedPanels(ctx, object) + if err != nil { + return nil, err + } + return object, nil +} + +func decodeEmbeddedPanels(ctx *transformationContext, object common.MapStr) (common.MapStr, error) { + embeddedPanelsValue, err := object.GetValue(panelsAttribute) + if err == common.ErrKeyNotFound { + return object, nil + } + if err != nil { + return nil, fmt.Errorf("retrieving embedded panels failed: %w", err) + } + embeddedPanels, ok := embeddedPanelsValue.([]map[string]any) + if !ok { + return nil, fmt.Errorf("expected list of panels, found %T", embeddedPanelsValue) + } + for i, panel := range embeddedPanels { + embeddableConfigValue, ok := panel[embeddableConfigAttribute] + if !ok { + continue + } + embeddableConfig, ok := embeddableConfigValue.(map[string]any) + if !ok { + return nil, fmt.Errorf("embeddable config is not a map, found %T", embeddableConfigValue) + } + embeddableConfig, err = decodeObject(ctx, common.MapStr(embeddableConfig)) + if err != nil { + return nil, fmt.Errorf("decoding embedded object failed: %w", err) + } + + panel[embeddableConfigAttribute] = embeddableConfig + embeddedPanels[i] = panel + } + object.Put(panelsAttribute, embeddedPanels) + return object, nil } diff --git a/test/packages/parallel/system/kibana/dashboard/system-5517a150-f9ce-11e6-8115-a7c18106d86a.json b/test/packages/parallel/system/kibana/dashboard/system-5517a150-f9ce-11e6-8115-a7c18106d86a.json index a546f5f4b3..16cfc1b14b 100644 --- a/test/packages/parallel/system/kibana/dashboard/system-5517a150-f9ce-11e6-8115-a7c18106d86a.json +++ b/test/packages/parallel/system/kibana/dashboard/system-5517a150-f9ce-11e6-8115-a7c18106d86a.json @@ -76,11 +76,191 @@ "embeddableConfig": { "attributes": { "description": "", - "layerListJSON": "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"985e7399-20df-464b-b6d5-880922106ffe\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"},{\"alpha\":0.75,\"id\":\"05b729fa-80a9-4215-aaed-4a8d9476e87d\",\"includeInFitToBounds\":true,\"joins\":[],\"label\":\"SSH failed login attempts source locations [Logs System]\",\"maxZoom\":24,\"minZoom\":0,\"sourceDescriptor\":{\"applyForceRefresh\":true,\"applyGlobalQuery\":true,\"applyGlobalTime\":true,\"geoField\":\"source.geo.location\",\"id\":\"80bac1cc-d19d-415d-93ad-f776fd099f24\",\"metrics\":[{\"type\":\"count\"}],\"requestType\":\"point\",\"resolution\":\"MOST_FINE\",\"type\":\"ES_GEO_GRID\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"style\":{\"isTimeAware\":true,\"properties\":{\"fillColor\":{\"options\":{\"color\":\"Yellow to Red\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"doc_count\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"type\":\"ORDINAL\"},\"type\":\"DYNAMIC\"},\"icon\":{\"options\":{\"value\":\"marker\"},\"type\":\"STATIC\"},\"iconOrientation\":{\"options\":{\"orientation\":0},\"type\":\"STATIC\"},\"iconSize\":{\"options\":{\"size\":6},\"type\":\"STATIC\"},\"labelBorderColor\":{\"options\":{\"color\":\"#FFFFFF\"},\"type\":\"STATIC\"},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}},\"labelColor\":{\"options\":{\"color\":\"#000000\"},\"type\":\"STATIC\"},\"labelSize\":{\"options\":{\"size\":14},\"type\":\"STATIC\"},\"labelText\":{\"options\":{\"value\":\"\"},\"type\":\"STATIC\"},\"lineColor\":{\"options\":{\"color\":\"#3d3d3d\"},\"type\":\"STATIC\"},\"lineWidth\":{\"options\":{\"size\":1},\"type\":\"STATIC\"},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}}},\"type\":\"VECTOR\"},\"type\":\"GEOJSON_VECTOR\",\"visible\":true}]", - "mapStateJSON": "{\"zoom\":1.58,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"system.auth.ssh.event:Failed OR system.auth.ssh.event:Invalid\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "layerListJSON": [ + { + "alpha": 1, + "id": "985e7399-20df-464b-b6d5-880922106ffe", + "includeInFitToBounds": true, + "label": null, + "maxZoom": 24, + "minZoom": 0, + "sourceDescriptor": { + "isAutoSelect": true, + "lightModeDefault": "road_map_desaturated", + "type": "EMS_TMS" + }, + "style": { + "type": "TILE" + }, + "type": "EMS_VECTOR_TILE", + "visible": true + }, + { + "alpha": 0.75, + "id": "05b729fa-80a9-4215-aaed-4a8d9476e87d", + "includeInFitToBounds": true, + "joins": [], + "label": "SSH failed login attempts source locations [Logs System]", + "maxZoom": 24, + "minZoom": 0, + "sourceDescriptor": { + "applyForceRefresh": true, + "applyGlobalQuery": true, + "applyGlobalTime": true, + "geoField": "source.geo.location", + "id": "80bac1cc-d19d-415d-93ad-f776fd099f24", + "indexPatternRefName": "layer_1_source_index_pattern", + "metrics": [ + { + "type": "count" + } + ], + "requestType": "point", + "resolution": "MOST_FINE", + "type": "ES_GEO_GRID" + }, + "style": { + "isTimeAware": true, + "properties": { + "fillColor": { + "options": { + "color": "Yellow to Red", + "colorCategory": "palette_0", + "field": { + "name": "doc_count", + "origin": "source" + }, + "fieldMetaOptions": { + "isEnabled": false, + "sigma": 3 + }, + "type": "ORDINAL" + }, + "type": "DYNAMIC" + }, + "icon": { + "options": { + "value": "marker" + }, + "type": "STATIC" + }, + "iconOrientation": { + "options": { + "orientation": 0 + }, + "type": "STATIC" + }, + "iconSize": { + "options": { + "size": 6 + }, + "type": "STATIC" + }, + "labelBorderColor": { + "options": { + "color": "#FFFFFF" + }, + "type": "STATIC" + }, + "labelBorderSize": { + "options": { + "size": "SMALL" + } + }, + "labelColor": { + "options": { + "color": "#000000" + }, + "type": "STATIC" + }, + "labelSize": { + "options": { + "size": 14 + }, + "type": "STATIC" + }, + "labelText": { + "options": { + "value": "" + }, + "type": "STATIC" + }, + "lineColor": { + "options": { + "color": "#3d3d3d" + }, + "type": "STATIC" + }, + "lineWidth": { + "options": { + "size": 1 + }, + "type": "STATIC" + }, + "symbolizeAs": { + "options": { + "value": "circle" + } + } + }, + "type": "VECTOR" + }, + "type": "GEOJSON_VECTOR", + "visible": true + } + ], + "mapStateJSON": { + "center": { + "lat": 19.94277, + "lon": 0 + }, + "filters": [], + "query": { + "language": "kuery", + "query": "system.auth.ssh.event:Failed OR system.auth.ssh.event:Invalid" + }, + "refreshConfig": { + "interval": 0, + "isPaused": true + }, + "settings": { + "autoFitToDataBounds": false, + "backgroundColor": "#ffffff", + "browserLocation": { + "zoom": 2 + }, + "disableInteractive": false, + "disableTooltipControl": false, + "fixedLocation": { + "lat": 0, + "lon": 0, + "zoom": 2 + }, + "hideLayerControl": false, + "hideToolbarOverlay": false, + "hideViewControl": false, + "initialLocation": "LAST_SAVED_LOCATION", + "maxZoom": 24, + "minZoom": 0, + "showScaleControl": false, + "showSpatialFilters": true, + "showTimesliderToggleButton": true, + "spatialFiltersAlpa": 0.3, + "spatialFiltersFillColor": "#DA8B45", + "spatialFiltersLineColor": "#DA8B45" + }, + "timeFilters": { + "from": "now-15m", + "to": "now" + }, + "zoom": 1.58 + }, "references": [], "title": "SSH failed login attempts source locations [Logs System]", - "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + "uiStateJSON": { + "isLayerTOCOpen": true, + "openTOCDetails": [] + } }, "enhancements": {}, "hiddenLayers": [], @@ -685,7 +865,15 @@ "source.geo.country_iso_code" ], "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[],\"highlightAll\":true,\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\",\"query\":{\"language\":\"kuery\",\"query\":\"data_stream.dataset:system.auth AND system.auth.ssh.event:*\"}}" + "searchSourceJSON": { + "filter": [], + "highlightAll": true, + "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.index", + "query": { + "language": "kuery", + "query": "data_stream.dataset:system.auth AND system.auth.ssh.event:*" + } + } }, "references": [ { @@ -733,9 +921,9 @@ "version": 1 }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-10-09T20:44:09.120Z", + "created_at": "2024-05-08T16:48:25.160Z", "id": "system-5517a150-f9ce-11e6-8115-a7c18106d86a", - "managed": false, + "managed": true, "references": [ { "id": "logs-*", @@ -780,4 +968,4 @@ ], "type": "dashboard", "typeMigrationVersion": "8.9.0" -} \ No newline at end of file +}