diff --git a/Makefile b/Makefile index 8e69bd9a76..f9cff57049 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ test-stack-command-with-basic-subscription: test-stack-command: test-stack-command-default test-stack-command-agent-version-flag test-stack-command-7x test-stack-command-800 test-stack-command-8x test-stack-command-9x test-stack-command-with-apm-server -test-check-packages: test-check-packages-with-kind test-check-packages-other test-check-packages-parallel test-check-packages-with-custom-agent test-check-packages-benchmarks test-check-packages-false-positives test-check-packages-with-logstash test-build-install-packages-composable +test-check-packages: test-check-packages-with-kind test-check-packages-other test-check-packages-parallel test-check-packages-with-custom-agent test-check-packages-benchmarks test-check-packages-false-positives test-check-packages-with-logstash test-check-packages-dashboards-as-code test-build-install-packages-composable test-check-packages-with-kind: PACKAGE_TEST_TYPE=with-kind ./scripts/test-check-packages.sh @@ -127,6 +127,9 @@ test-check-packages-parallel: test-check-packages-with-custom-agent: PACKAGE_TEST_TYPE=with-custom-agent ./scripts/test-check-packages.sh +test-check-packages-dashboards-as-code: + ./scripts/test-check-packages-dashboards-as-code.sh + test-build-install-packages-composable: ./scripts/test-composable-packages.sh diff --git a/cmd/build.go b/cmd/build.go index 29d577c479..13bf1f30d8 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -14,6 +14,7 @@ import ( "github.com/elastic/elastic-package/internal/cobraext" "github.com/elastic/elastic-package/internal/files" "github.com/elastic/elastic-package/internal/install" + "github.com/elastic/elastic-package/internal/kibana" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/packages" "github.com/elastic/elastic-package/internal/profile" @@ -45,6 +46,7 @@ func setupBuildCommand() *cobraext.Command { cmd.Flags().Bool(cobraext.BuildZipFlagName, true, cobraext.BuildZipFlagDescription) cmd.Flags().Bool(cobraext.SignPackageFlagName, false, cobraext.SignPackageFlagDescription) cmd.Flags().Bool(cobraext.BuildSkipValidationFlagName, false, cobraext.BuildSkipValidationFlagDescription) + cmd.Flags().Bool(cobraext.BuildCompileDashboardsAsCodeFlagName, false, cobraext.BuildCompileDashboardsAsCodeFlagDescription) return cobraext.NewCommand(cmd, cobraext.ContextPackage) } @@ -54,6 +56,7 @@ func buildCommandAction(cmd *cobra.Command, args []string) error { createZip, _ := cmd.Flags().GetBool(cobraext.BuildZipFlagName) signPackage, _ := cmd.Flags().GetBool(cobraext.SignPackageFlagName) skipValidation, _ := cmd.Flags().GetBool(cobraext.BuildSkipValidationFlagName) + compileDashboardsAsCode, _ := cmd.Flags().GetBool(cobraext.BuildCompileDashboardsAsCodeFlagName) if signPackage && !createZip { return errors.New("can't sign the unzipped package, please use also the --zip switch") @@ -102,7 +105,15 @@ func buildCommandAction(cmd *cobra.Command, args []string) error { requiredInputsResolver := requiredinputs.NewRequiredInputsResolver(eprClient) - target, err := builder.BuildPackage(builder.BuildOptions{ + var kibanaClient *kibana.Client + if compileDashboardsAsCode { + kibanaClient, err = stack.NewKibanaClientFromProfile(prof) + if err != nil { + return fmt.Errorf("can't create Kibana client for dashboards-as-code compilation: %w", err) + } + } + + target, err := builder.BuildPackage(cmd.Context(), builder.BuildOptions{ PackageRoot: packageRoot, BuildDir: buildDir, CreateZip: createZip, @@ -112,6 +123,7 @@ func buildCommandAction(cmd *cobra.Command, args []string) error { UpdateReadmes: true, SchemaURLs: appConfig.SchemaURLs(), RequiredInputsResolver: requiredInputsResolver, + KibanaClient: kibanaClient, }) if err != nil { return fmt.Errorf("building package failed: %w", err) diff --git a/internal/builder/dashboards_as_code.go b/internal/builder/dashboards_as_code.go new file mode 100644 index 0000000000..2ffdaf3b93 --- /dev/null +++ b/internal/builder/dashboards_as_code.go @@ -0,0 +1,123 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package builder + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver/v3" + + "github.com/elastic/elastic-package/internal/export" + "github.com/elastic/elastic-package/internal/kibana" + "github.com/elastic/elastic-package/internal/logger" + "github.com/elastic/elastic-package/internal/packages" +) + +const dashboardsAsCodeDir = "_dev/shared" + +// minDashboardsAsCodeKibanaVersion is the first Kibana version that supports +// the dashboards-as-code import API (POST /api/dashboards). +var minDashboardsAsCodeKibanaVersion = semver.MustParse("9.4.0") + +// compileDashboardsAsCode compiles each *.json file under +// /_dev/shared/ into a saved-object dashboard under +// /kibana/dashboard/. Each source file is imported into +// the connected Kibana via PUT /api/dashboards/, the resulting dashboard +// is exported back through the standard dashboards export pipeline, and the +// imported saved object is then deleted from Kibana. +// +// Compilation is opt-in: callers signal intent by passing a non-nil +// kibanaClient. When kibanaClient is nil this function returns immediately, +// even if source files are present, so packages that use _dev/shared/ for +// unrelated content are unaffected by builds that did not request +// dashboards-as-code compilation. +func compileDashboardsAsCode(ctx context.Context, kibanaClient *kibana.Client, sourcePackageRoot string) error { + if kibanaClient == nil { + return nil + } + + sourceDir := filepath.Join(sourcePackageRoot, dashboardsAsCodeDir) + files, err := filepath.Glob(filepath.Join(sourceDir, "*.json")) + if err != nil { + return fmt.Errorf("listing dashboards-as-code sources failed: %w", err) + } + if len(files) == 0 { + return nil + } + + versionInfo, err := kibanaClient.Version() + if err != nil { + return fmt.Errorf("getting Kibana version information: %w", err) + } + if err := checkDashboardsAsCodeKibanaVersion(versionInfo); err != nil { + return err + } + + manifest, err := packages.ReadPackageManifestFromPackageRoot(sourcePackageRoot) + if err != nil { + return fmt.Errorf("reading package manifest failed (path: %s): %w", sourcePackageRoot, err) + } + + for _, file := range files { + if err := compileDashboardAsCodeFile(ctx, kibanaClient, manifest.Name, sourcePackageRoot, file); err != nil { + return fmt.Errorf("compiling dashboards-as-code file %s: %w", file, err) + } + } + return nil +} + +func checkDashboardsAsCodeKibanaVersion(info kibana.VersionInfo) error { + v, err := semver.NewVersion(info.Number) + if err != nil { + return fmt.Errorf("cannot parse Kibana version %s: %w", info.Number, err) + } + if v.LessThan(minDashboardsAsCodeKibanaVersion) { + return fmt.Errorf("dashboards-as-code requires Kibana %s or later (got %s); the import API at POST /api/dashboards is not available in this version", + minDashboardsAsCodeKibanaVersion, info.Number) + } + return nil +} + +func compileDashboardAsCodeFile(ctx context.Context, kibanaClient *kibana.Client, packageName, sourcePackageRoot, file string) error { + logger.Debugf("Compiling dashboards-as-code file: %s", file) + + body, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("reading dashboards-as-code source failed: %w", err) + } + + // Use the source filename (without extension) as the saved-object id so the + // compiled output is deterministic. standardizeObjectID will then prefix it + // with the package name during export. + id := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) + id, err = kibanaClient.ImportDashboardAsCode(ctx, id, body) + if err != nil { + return fmt.Errorf("importing dashboards-as-code failed: %w", err) + } + + // Best-effort cleanup of the imported dashboard, regardless of how the + // rest of this function completes. Detach from ctx's cancellation so + // cleanup still runs if the parent context has been cancelled. + cleanupCtx := context.WithoutCancel(ctx) + defer func() { + if cleanupErr := kibanaClient.DeleteDashboard(cleanupCtx, id); cleanupErr != nil { + logger.Warnf("Failed to delete imported dashboard %s during cleanup: %v", id, cleanupErr) + } + }() + + objects, err := kibanaClient.Export(ctx, []string{id}) + if err != nil { + return fmt.Errorf("exporting dashboard %s failed: %w", id, err) + } + + if err := export.TransformAndWriteDashboards(sourcePackageRoot, packageName, objects); err != nil { + return fmt.Errorf("writing exported dashboard %s failed: %w", id, err) + } + return nil +} diff --git a/internal/builder/packages.go b/internal/builder/packages.go index 9784e7742e..dd092cc600 100644 --- a/internal/builder/packages.go +++ b/internal/builder/packages.go @@ -5,6 +5,7 @@ package builder import ( + "context" "errors" "fmt" "os" @@ -16,6 +17,7 @@ import ( "github.com/elastic/elastic-package/internal/environment" "github.com/elastic/elastic-package/internal/fields" "github.com/elastic/elastic-package/internal/files" + "github.com/elastic/elastic-package/internal/kibana" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/packages" "github.com/elastic/elastic-package/internal/requiredinputs" @@ -38,6 +40,12 @@ type BuildOptions struct { UpdateReadmes bool SchemaURLs fields.SchemaURLs RequiredInputsResolver requiredinputs.Resolver + + // KibanaClient is used by the dashboards-as-code build step to import the + // new-format dashboards under _dev/dashboards_as_code/ and re-export them + // in the saved-object format. May be nil when the package does not use the + // dashboards-as-code feature. + KibanaClient *kibana.Client } // BuildDirectory function locates the target build directory. If the directory doesn't exist, it will create it. @@ -168,7 +176,7 @@ func FindBuildPackagesDirectory() (string, bool, error) { } // BuildPackage function builds the package. -func BuildPackage(options BuildOptions) (string, error) { +func BuildPackage(ctx context.Context, options BuildOptions) (string, error) { // buildPackageRoot is the directory where the built package content is placed // eg. /packages// buildPackageRoot, err := BuildPackagesDirectory(options.PackageRoot, options.BuildDir) @@ -183,6 +191,12 @@ func BuildPackage(options BuildOptions) (string, error) { return "", fmt.Errorf("clearing package contents failed: %w", err) } + logger.Debug("Compile dashboards-as-code") + err = compileDashboardsAsCode(ctx, options.KibanaClient, options.PackageRoot) + if err != nil { + return "", fmt.Errorf("compiling dashboards-as-code failed: %w", err) + } + logger.Debugf("Copy package content (source: %s)", options.PackageRoot) err = files.CopyWithoutDev(options.PackageRoot, buildPackageRoot) if err != nil { diff --git a/internal/cobraext/flags.go b/internal/cobraext/flags.go index e8c93e1348..ee84df54f6 100644 --- a/internal/cobraext/flags.go +++ b/internal/cobraext/flags.go @@ -94,6 +94,9 @@ const ( BuildZipFlagName = "zip" BuildZipFlagDescription = "archive the built package" + BuildCompileDashboardsAsCodeFlagName = "compile-dashboards-as-code" + BuildCompileDashboardsAsCodeFlagDescription = "compile dashboards-as-code sources under _dev/shared/*.json into kibana/dashboard/ saved objects (requires a running Kibana 9.4.0+ stack)" + ChangelogAddNextFlagName = "next" ChangelogAddNextFlagDescription = "changelog entry is added in the next `major`, `minor` or `patch` version" diff --git a/internal/export/dashboards.go b/internal/export/dashboards.go index bf178043c6..08b39080a0 100644 --- a/internal/export/dashboards.go +++ b/internal/export/dashboards.go @@ -46,17 +46,23 @@ func Dashboards(ctx context.Context, kibanaClient *kibana.Client, dashboardsIDs return fmt.Errorf("exporting dashboards using Kibana client failed: %w", err) } + return TransformAndWriteDashboards(packageRoot, m.Name, objects) +} + +// TransformAndWriteDashboards applies the standard dashboard transformation pipeline +// to a list of Kibana saved objects and writes them under packageRoot/kibana//.json. +// It is shared by the dashboards export command and the dashboards-as-code build step. +func TransformAndWriteDashboards(packageRoot, packageName string, objects []common.MapStr) error { transformContext := &transformationContext{ - packageName: m.Name, + packageName: packageName, } - objects, err = applyTransformations(transformContext, objects) + objects, err := applyTransformations(transformContext, objects) if err != nil { return fmt.Errorf("can't transform Kibana objects: %w", err) } - err = saveObjectsToFiles(packageRoot, objects) - if err != nil { + if err := saveObjectsToFiles(packageRoot, objects); err != nil { return fmt.Errorf("can't save Kibana objects: %w", err) } return nil diff --git a/internal/export/ingest_pipelines_test.go b/internal/export/ingest_pipelines_test.go index bc9404c3a4..951b21298a 100644 --- a/internal/export/ingest_pipelines_test.go +++ b/internal/export/ingest_pipelines_test.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package export +package export_test import ( "errors" @@ -20,6 +20,7 @@ import ( "gopkg.in/dnaeon/go-vcr.v3/cassette" "gopkg.in/yaml.v3" + "github.com/elastic/elastic-package/internal/export" "github.com/elastic/elastic-package/internal/stack" estest "github.com/elastic/elastic-package/internal/elasticsearch/test" @@ -76,7 +77,7 @@ func (s *ingestPipelineExportSuite) SetupTest() { writeAssignments := createTestWriteAssignments(s.PipelineIds, s.ExportDir) - err = IngestPipelines(s.T().Context(), client.API, writeAssignments) + err = export.IngestPipelines(s.T().Context(), client.API, writeAssignments) s.Require().NoError(err) } else { @@ -89,7 +90,7 @@ func (s *ingestPipelineExportSuite) TestExportPipelines() { outputDir := s.T().TempDir() writeAssignments := createTestWriteAssignments(s.PipelineIds, outputDir) - err := IngestPipelines(s.T().Context(), client.API, writeAssignments) + err := export.IngestPipelines(s.T().Context(), client.API, writeAssignments) s.Require().NoError(err) filesExpected := countFiles(s.T(), s.ExportDir) @@ -102,12 +103,12 @@ func (s *ingestPipelineExportSuite) TestExportPipelines() { assertEqualExports(s.T(), s.ExportDir, outputDir) } -func createTestWriteAssignments(pipelineIDs []string, outputDir string) PipelineWriteAssignments { - writeAssignments := make(PipelineWriteAssignments) +func createTestWriteAssignments(pipelineIDs []string, outputDir string) export.PipelineWriteAssignments { + writeAssignments := make(export.PipelineWriteAssignments) for _, pipelineID := range pipelineIDs { - writeAssignments[pipelineID] = PipelineWriteLocation{ - Type: PipelineWriteLocationTypeRoot, + writeAssignments[pipelineID] = export.PipelineWriteLocation{ + Type: export.PipelineWriteLocationTypeRoot, Name: pipelineID, ParentPath: outputDir, } diff --git a/internal/kibana/dashboards_as_code.go b/internal/kibana/dashboards_as_code.go new file mode 100644 index 0000000000..b9c5dd48fb --- /dev/null +++ b/internal/kibana/dashboards_as_code.go @@ -0,0 +1,65 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package kibana + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/elastic/elastic-package/internal/logger" +) + +// DashboardsAPI is the prefix for the Kibana dashboards-as-code import API. +const DashboardsAPI = "/api/dashboards" + +// ImportDashboardAsCode imports a dashboards-as-code JSON document via the +// /api/dashboards Kibana endpoint, storing it under the supplied id (the PUT +// form of the API guarantees the resulting saved object uses the id we choose, +// rather than a freshly generated one). Returns the id reported by Kibana, +// which should match the supplied id. +func (c *Client) ImportDashboardAsCode(ctx context.Context, id string, body []byte) (string, error) { + if id == "" { + return "", errors.New("dashboards-as-code import requires a non-empty id") + } + logger.Debugf("Import dashboards-as-code via Kibana dashboards API (id: %s)", id) + + path := fmt.Sprintf("%s/%s", DashboardsAPI, id) + statusCode, respBody, err := c.put(ctx, path, body) + if err != nil { + return "", fmt.Errorf("could not import dashboards-as-code; API status code = %d; response body = %s: %w", statusCode, string(respBody), err) + } + if statusCode != http.StatusOK && statusCode != http.StatusCreated { + return "", fmt.Errorf("could not import dashboards-as-code; API status code = %d; response body = %s", statusCode, string(respBody)) + } + + var resp struct { + ID string `json:"id"` + } + if err := json.Unmarshal(respBody, &resp); err != nil { + return "", fmt.Errorf("could not decode dashboards-as-code import response (body: %s): %w", respBody, err) + } + if resp.ID == "" { + return "", errors.New("dashboards-as-code import response did not include an id") + } + return resp.ID, nil +} + +// DeleteDashboard removes a dashboard by id via the dashboards-as-code API. +// This is used to clean up dashboards that were imported during the +// dashboards-as-code build step. +func (c *Client) DeleteDashboard(ctx context.Context, id string) error { + path := fmt.Sprintf("%s/%s", DashboardsAPI, id) + statusCode, respBody, err := c.delete(ctx, path) + if err != nil { + return fmt.Errorf("could not delete dashboard %s; API status code = %d; response body = %s: %w", id, statusCode, string(respBody), err) + } + if statusCode != http.StatusOK && statusCode != http.StatusNoContent && statusCode != http.StatusNotFound { + return fmt.Errorf("could not delete dashboard %s; API status code = %d; response body = %s", id, statusCode, string(respBody)) + } + return nil +} diff --git a/internal/packages/archetype/package_test.go b/internal/packages/archetype/package_test.go index 965d9c7544..b3740ef0c7 100644 --- a/internal/packages/archetype/package_test.go +++ b/internal/packages/archetype/package_test.go @@ -5,6 +5,7 @@ package archetype import ( + "context" "os" "path/filepath" "testing" @@ -101,7 +102,7 @@ func buildPackage(t *testing.T, repositoryRoot *os.Root, packageRoot string) err err := os.MkdirAll(buildDir, 0o755) require.NoError(t, err) - _, err = builder.BuildPackage(builder.BuildOptions{ + _, err = builder.BuildPackage(context.Background(), builder.BuildOptions{ PackageRoot: packageRoot, BuildDir: buildDir, RepositoryRoot: repositoryRoot, diff --git a/internal/packages/installer/factory.go b/internal/packages/installer/factory.go index d7a3a7bbec..63d9cf8b57 100644 --- a/internal/packages/installer/factory.go +++ b/internal/packages/installer/factory.go @@ -91,7 +91,7 @@ func NewForPackage(options Options) (Installer, error) { return CreateForZip(options.Kibana, options.ZipPath) } - target, err := builder.BuildPackage(builder.BuildOptions{ + target, err := builder.BuildPackage(context.TODO(), builder.BuildOptions{ PackageRoot: options.PackageRoot, CreateZip: supportsUploadZip, SignPackage: false, @@ -100,6 +100,7 @@ func NewForPackage(options Options) (Installer, error) { UpdateReadmes: false, SchemaURLs: options.SchemaURLs, RequiredInputsResolver: options.RequiredInputsResolver, + KibanaClient: options.Kibana, }) if err != nil { return nil, fmt.Errorf("failed to build package: %w", err) diff --git a/scripts/test-check-packages-dashboards-as-code.sh b/scripts/test-check-packages-dashboards-as-code.sh new file mode 100755 index 0000000000..73925f9148 --- /dev/null +++ b/scripts/test-check-packages-dashboards-as-code.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Tests the dashboards-as-code build flow against a real Kibana. +# +# For each fixture under test/packages/dashboards_as_code/, this script: +# 1. Brings up the stack (the build step needs Kibana to compile dashboards). +# 2. Asserts the fixture has no compiled saved object yet (the build must +# produce it, not just copy a pre-existing one). +# 3. Runs `elastic-package lint` then `elastic-package build +# --compile-dashboards-as-code` (the equivalent of `check` but with +# the opt-in flag wired to build). +# 4. Asserts that for every .json source under +# _dev/build/dashboards_as_code/ the build wrote +# kibana/dashboard/-.json. +# +# The compiled dashboard files are not committed: any kibana/dashboard +# content that survives a run is removed in the cleanup trap. + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +source "${SCRIPT_DIR}/stack_parameters.sh" +source "${SCRIPT_DIR}/stack_helpers.sh" + +set -euxo pipefail + +PACKAGES_PATH="test/packages/dashboards_as_code" + +cleanup() { + local r=$? + if [ "${r}" -ne 0 ]; then + echo "^^^ +++" + fi + echo "~~~ elastic-package cleanup" + + if is_stack_created; then + elastic-package stack dump -v \ + --output "build/elastic-stack-dump/check-dashboards-as-code" || true + elastic-package stack down -v + fi + + for d in "${PACKAGES_PATH}"/*/; do + elastic-package clean -C "$d" -v || true + # Remove freshly-compiled dashboard artifacts so the working tree stays clean. + rm -rf "${d}kibana/dashboard" + if [ -d "${d}kibana" ] && [ -z "$(ls -A "${d}kibana")" ]; then + rmdir "${d}kibana" + fi + done + + exit $r +} + +trap cleanup EXIT + +ELASTIC_PACKAGE_LINKS_FILE_PATH="$(pwd)/scripts/links_table.yml" +export ELASTIC_PACKAGE_LINKS_FILE_PATH + +echo "--- Prepare Elastic stack" +stack_args=$(set +x; stack_version_args) +stack_args="${stack_args} $(set +x; stack_provider_args)" +elastic-package stack update -v ${stack_args} +elastic-package stack up -d -v ${stack_args} +elastic-package stack status + +# Read package name from manifest.yml without a YAML parser. +package_name() { + local manifest="$1/manifest.yml" + awk -F': ' '$1 == "name" { gsub(/[" ]/, "", $2); print $2; exit }' "$manifest" +} + +for d in "${PACKAGES_PATH}"/*/; do + package_name=$(package_name "$d") + echo "--- Checking package ${d} (name: ${package_name})" + + # Sanity-check the fixture: the compiled saved object must NOT exist yet. + # If it does, the test would not actually be exercising the build step. + if compgen -G "${d}kibana/dashboard/*.json" > /dev/null; then + echo "Fixture ${d} already contains compiled dashboards; remove them so the test exercises the build step." + exit 1 + fi + + elastic-package lint -C "$d" -v + elastic-package build -C "$d" -v --compile-dashboards-as-code + + # For every source, the build must have written the standardised SO. + for source in "${d}_dev/shared"/*.json; do + source_id=$(basename "${source}" .json) + expected="${d}kibana/dashboard/${package_name}-${source_id}.json" + if [ ! -f "${expected}" ]; then + echo "Build did not produce expected dashboard: ${expected}" + exit 1 + fi + echo " OK: ${expected}" + done +done diff --git a/test/packages/dashboards_as_code/sample/_dev/shared/overview.json b/test/packages/dashboards_as_code/sample/_dev/shared/overview.json new file mode 100644 index 0000000000..b9c05e8d78 --- /dev/null +++ b/test/packages/dashboards_as_code/sample/_dev/shared/overview.json @@ -0,0 +1,37 @@ +{ + "options": { + "hide_panel_titles": false, + "hide_panel_borders": false, + "use_margins": true, + "auto_apply_filters": true, + "sync_colors": false, + "sync_cursor": true, + "sync_tooltips": false + }, + "query": { + "expression": "", + "language": "kql" + }, + "title": "Dashboard [2026-05-07T16:07:44.633+01:00]", + "panels": [ + { + "grid": { + "y": 0, + "x": 0, + "w": 6, + "h": 2 + }, + "config": { + "hide_title": true, + "hide_border": true, + "content": "test dashboard", + "settings": { + "open_links_in_new_tab": true + } + }, + "id": "87c99aa8-295a-4d09-aad9-8e45d1c2ee1b", + "type": "markdown" + } + ], + "pinned_panels": [] +} diff --git a/test/packages/dashboards_as_code/sample/changelog.yml b/test/packages/dashboards_as_code/sample/changelog.yml new file mode 100644 index 0000000000..e00f881335 --- /dev/null +++ b/test/packages/dashboards_as_code/sample/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.0.1" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 diff --git a/test/packages/dashboards_as_code/sample/docs/README.md b/test/packages/dashboards_as_code/sample/docs/README.md new file mode 100644 index 0000000000..45478b368c --- /dev/null +++ b/test/packages/dashboards_as_code/sample/docs/README.md @@ -0,0 +1,5 @@ +# Dashboards-as-code Sample + +Fixture exercising the dashboards-as-code build step. The dashboard +source lives in `_dev/shared/` and is compiled into `kibana/dashboard/` +during `elastic-package build` against a running Kibana 9.4.0+ stack. diff --git a/test/packages/dashboards_as_code/sample/manifest.yml b/test/packages/dashboards_as_code/sample/manifest.yml new file mode 100644 index 0000000000..8326f1fe99 --- /dev/null +++ b/test/packages/dashboards_as_code/sample/manifest.yml @@ -0,0 +1,14 @@ +format_version: 3.6.0 +name: sample +title: "Dashboards-as-code Sample" +version: 0.0.1 +description: "Fixture exercising the dashboards-as-code build step." +type: integration +categories: + - custom +conditions: + kibana: + version: "^9.4.0" +owner: + github: elastic/integrations + type: elastic