diff --git a/.ci/.storybook/main.js b/.ci/.storybook/main.js new file mode 100644 index 0000000000000..e399ec087e168 --- /dev/null +++ b/.ci/.storybook/main.js @@ -0,0 +1,28 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +const config = require('@kbn/storybook').defaultConfig; +const aliases = require('../../src/dev/storybook/aliases.ts').storybookAliases; + +config.refs = {}; + +for (const alias of Object.keys(aliases).filter((a) => a !== 'ci_composite')) { + // snake_case -> Title Case + const title = alias + .replace(/_/g, ' ') + .split(' ') + .map((n) => n[0].toUpperCase() + n.slice(1)) + .join(' '); + + config.refs[alias] = { + title: title, + url: `${process.env.STORYBOOK_BASE_URL}/${alias}`, + }; +} + +module.exports = config; diff --git a/.eslintignore b/.eslintignore index ea8ab55ad7726..4559711bb9dd3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,6 +15,7 @@ node_modules target snapshots.js +!/.ci !/.eslintrc.js !.storybook diff --git a/packages/kbn-storybook/lib/default_config.ts b/packages/kbn-storybook/lib/default_config.ts index 53c51e9cf29fe..1b049761a3a98 100644 --- a/packages/kbn-storybook/lib/default_config.ts +++ b/packages/kbn-storybook/lib/default_config.ts @@ -14,4 +14,11 @@ export const defaultConfig: StorybookConfig = { typescript: { reactDocgen: false, }, + webpackFinal: (config, options) => { + if (process.env.CI) { + config.parallelism = 4; + config.cache = true; + } + return config; + }, }; diff --git a/packages/kbn-storybook/lib/templates/index.ejs b/packages/kbn-storybook/lib/templates/index.ejs index a4f8204c95d7a..b193c87824d40 100644 --- a/packages/kbn-storybook/lib/templates/index.ejs +++ b/packages/kbn-storybook/lib/templates/index.ejs @@ -16,12 +16,12 @@ - - - - + + + + <% if (typeof headHtmlSnippet !== 'undefined') { %> <%= headHtmlSnippet %> <% } %> <% diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index c72c81f489fb9..f1a3737747573 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +// Please also add new aliases to test/scripts/jenkins_storybook.sh export const storybookAliases = { apm: 'x-pack/plugins/apm/.storybook', canvas: 'x-pack/plugins/canvas/storybook', codeeditor: 'src/plugins/kibana_react/public/code_editor/.storybook', + ci_composite: '.ci/.storybook', url_template_editor: 'src/plugins/kibana_react/public/url_template_editor/.storybook', dashboard: 'src/plugins/dashboard/.storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh new file mode 100755 index 0000000000000..8ebfc1035fe1f --- /dev/null +++ b/test/scripts/jenkins_storybook.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd "$XPACK_DIR/plugins/canvas" +node scripts/storybook --dll + +cd "$KIBANA_DIR" + +# yarn storybook --site apm # TODO re-enable after being fixed +yarn storybook --site canvas +yarn storybook --site ci_composite +yarn storybook --site url_template_editor +yarn storybook --site codeeditor +yarn storybook --site dashboard +yarn storybook --site dashboard_enhanced +yarn storybook --site data_enhanced +yarn storybook --site embeddable +yarn storybook --site infra +yarn storybook --site security_solution +yarn storybook --site ui_actions_enhanced +yarn storybook --site observability +yarn storybook --site presentation diff --git a/vars/githubCommitStatus.groovy b/vars/githubCommitStatus.groovy index 248d226169a61..175dbe0c90542 100644 --- a/vars/githubCommitStatus.groovy +++ b/vars/githubCommitStatus.groovy @@ -41,13 +41,15 @@ def trackBuild(commit, context, Closure closure) { } // state: error|failure|pending|success -def create(sha, state, description, context) { +def create(sha, state, description, context, targetUrl = null) { + targetUrl = targetUrl ?: env.BUILD_URL + withGithubCredentials { return githubApi.post("repos/elastic/kibana/statuses/${sha}", [ state: state, description: description, context: context, - target_url: env.BUILD_URL + target_url: targetUrl.toString() ]) } } diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index eead00c082ba7..da6b1d8c51ce6 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -169,12 +169,18 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ? getBuildStatusIncludingMetrics() : buildUtils.getBuildStatus() + def storybooksUrl = buildState.get('storybooksUrl') + def storybooksMessage = storybooksUrl ? "* [Storybooks Preview](${storybooksUrl})" : "* Storybooks not built" + if (!isFinal) { + storybooksMessage = storybooksUrl ? storybooksMessage : "* Storybooks not built yet" + def failuresPart = status != 'SUCCESS' ? ', with failures' : '' messages << """ ## :hourglass_flowing_sand: Build in-progress${failuresPart} * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} * This comment will update when the build is complete """ } else if (status == 'SUCCESS') { @@ -182,6 +188,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ## :green_heart: Build Succeeded * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} ${getDocsChangesLink()} """ } else if(status == 'UNSTABLE') { @@ -189,6 +196,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ## :yellow_heart: Build succeeded, but was flaky * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} ${getDocsChangesLink()} """.stripIndent() @@ -204,6 +212,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ## :broken_heart: Build Failed * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} * [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps) * [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html) ${getDocsChangesLink()} diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 7adf755bfc583..1fe1d78658669 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -460,6 +460,7 @@ def allCiTasks() { tasks.test() tasks.functionalOss() tasks.functionalXpack() + tasks.storybooksCi() } }, jest: { diff --git a/vars/storybooks.groovy b/vars/storybooks.groovy new file mode 100644 index 0000000000000..f3c4a97a7d436 --- /dev/null +++ b/vars/storybooks.groovy @@ -0,0 +1,83 @@ +def getStorybooksBucket() { + return "ci-artifacts.kibana.dev/storybooks" +} + +def getDestinationDir() { + return env.ghprbPullId ? "pr-${env.ghprbPullId}" : buildState.get('checkoutInfo').branch.replace("/", "__") +} + +def getUrl() { + return "https://${getStorybooksBucket()}/${getDestinationDir()}" +} + +def getUrlLatest() { + return "${getUrl()}/latest" +} + +def getUrlForCommit() { + return "${getUrl()}/${buildState.get('checkoutInfo').commit}" +} + +def upload() { + dir("built_assets/storybook") { + sh "mv ci_composite composite" + + def storybooks = sh( + script: 'ls -1d */', + returnStdout: true + ).trim() + .split('\n') + .collect { it.replace('/', '') } + .findAll { it != 'composite' } + + def listHtml = storybooks.collect { """
  • ${it}
  • """ }.join("\n") + + def html = """ + + +

    Storybooks

    +

    Composite Storybook

    +

    All

    + + + + """ + + writeFile(file: 'index.html', text: html) + + withGcpServiceAccount.fromVaultSecret('secret/kibana-issues/dev/ci-artifacts-key', 'value') { + kibanaPipeline.bash(""" + gsutil -q -m cp -r -z js,css,html,json,map,txt,svg '*' 'gs://${getStorybooksBucket()}/${getDestinationDir()}/${buildState.get('checkoutInfo').commit}/' + gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp -z html 'index.html' 'gs://${getStorybooksBucket()}/${getDestinationDir()}/latest/' + """, "Upload Storybooks to GCS") + } + + buildState.set('storybooksUrl', getUrlForCommit()) + } +} + +def build() { + withEnv(["STORYBOOK_BASE_URL=${getUrlForCommit()}"]) { + kibanaPipeline.bash('test/scripts/jenkins_storybook.sh', 'Build Storybooks') + } +} + +def buildAndUpload() { + def sha = buildState.get('checkoutInfo').commit + def context = 'Build and Publish Storybooks' + + githubCommitStatus.create(sha, 'pending', 'Building Storybooks', context) + + try { + build() + upload() + githubCommitStatus.create(sha, 'success', 'Storybooks built', context, getUrlForCommit()) + } catch(ex) { + githubCommitStatus.create(sha, 'error', 'Building Storybooks failed', context) + throw ex + } +} + +return this diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 7c40966ff5e04..846eed85fb076 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -124,4 +124,10 @@ def functionalXpack(Map params = [:]) { } } +def storybooksCi() { + task { + storybooks.buildAndUpload() + } +} + return this diff --git a/x-pack/plugins/canvas/storybook/preview-head.html b/x-pack/plugins/canvas/storybook/preview-head.html index bef08a5120a36..f8a7de6ddbaf1 100644 --- a/x-pack/plugins/canvas/storybook/preview-head.html +++ b/x-pack/plugins/canvas/storybook/preview-head.html @@ -2,5 +2,5 @@ This file is looked for by Storybook and included in the HEAD element if it exists. This is how we load the DLL content into the Storybook UI. --> - - + +