mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[home] Sample Data Tab w/ callout UI (#136790)
* [home] Sample Data Tab w/ callout UI * Fix tests * Update packages/home/sample_data_tab_content/src/demo_env_panel.tsx Co-authored-by: Kelly Murphy <kelly.murphy@elastic.co> * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Fixes * Fixes * Update test/functional/page_objects/home_page.ts * Fix tests * Add telemetry * Add docs, more telemetry Co-authored-by: Kelly Murphy <kelly.murphy@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
7717e90b4e
commit
b459ffa4c6
65 changed files with 895 additions and 89 deletions
145
packages/home/sample_data_card/BUILD.bazel
Normal file
145
packages/home/sample_data_card/BUILD.bazel
Normal file
|
@ -0,0 +1,145 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_config")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
|
||||
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
|
||||
|
||||
PKG_DIRNAME = "sample_data_card"
|
||||
PKG_REQUIRE_NAME = "@kbn/home-sample-data-card"
|
||||
|
||||
SOURCE_FILES = glob(
|
||||
[
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.mdx",
|
||||
"src/**/*.svg",
|
||||
"src/**/*.png",
|
||||
],
|
||||
exclude = [
|
||||
"**/*.test.*",
|
||||
],
|
||||
)
|
||||
|
||||
SRCS = SOURCE_FILES
|
||||
|
||||
filegroup(
|
||||
name = "srcs",
|
||||
srcs = SRCS,
|
||||
)
|
||||
|
||||
NPM_MODULE_EXTRA_FILES = [
|
||||
"package.json",
|
||||
]
|
||||
|
||||
# In this array place runtime dependencies, including other packages and NPM packages
|
||||
# which must be available for this code to run.
|
||||
#
|
||||
# To reference other packages use:
|
||||
# "//repo/relative/path/to/package"
|
||||
# eg. "//packages/kbn-utils"
|
||||
#
|
||||
# To reference a NPM package use:
|
||||
# "@npm//name-of-package"
|
||||
# eg. "@npm//lodash"
|
||||
RUNTIME_DEPS = [
|
||||
"@npm//@elastic/eui",
|
||||
"@npm//@storybook/addon-actions",
|
||||
"@npm//@storybook/react",
|
||||
"@npm//enzyme",
|
||||
"@npm//lodash",
|
||||
"@npm//react",
|
||||
"//packages/kbn-i18n",
|
||||
]
|
||||
|
||||
# In this array place dependencies necessary to build the types, which will include the
|
||||
# :npm_module_types target of other packages and packages from NPM, including @types/*
|
||||
# packages.
|
||||
#
|
||||
# To reference the types for another package use:
|
||||
# "//repo/relative/path/to/package:npm_module_types"
|
||||
# eg. "//packages/kbn-utils:npm_module_types"
|
||||
#
|
||||
# References to NPM packages work the same as RUNTIME_DEPS
|
||||
TYPES_DEPS = [
|
||||
"@npm//@elastic/eui",
|
||||
"@npm//@storybook/addon-actions",
|
||||
"@npm//@storybook/react",
|
||||
"@npm//@types/enzyme",
|
||||
"@npm//@types/jest",
|
||||
"@npm//@types/lodash",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/react",
|
||||
"//packages/kbn-ambient-ui-types",
|
||||
"//packages/kbn-i18n:npm_module_types",
|
||||
"//packages/home/sample_data_types",
|
||||
]
|
||||
|
||||
jsts_transpiler(
|
||||
name = "target_node",
|
||||
srcs = SRCS,
|
||||
build_pkg_name = package_name(),
|
||||
)
|
||||
|
||||
jsts_transpiler(
|
||||
name = "target_web",
|
||||
srcs = SRCS,
|
||||
build_pkg_name = package_name(),
|
||||
web = True,
|
||||
additional_args = [
|
||||
"--copy-files",
|
||||
"--quiet"
|
||||
],
|
||||
)
|
||||
|
||||
ts_config(
|
||||
name = "tsconfig",
|
||||
src = "tsconfig.json",
|
||||
deps = [
|
||||
"//:tsconfig.base.json",
|
||||
"//:tsconfig.bazel.json",
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "tsc_types",
|
||||
args = ['--pretty'],
|
||||
srcs = SRCS,
|
||||
deps = TYPES_DEPS,
|
||||
declaration = True,
|
||||
emit_declaration_only = True,
|
||||
out_dir = "target_types",
|
||||
root_dir = "src",
|
||||
tsconfig = ":tsconfig",
|
||||
)
|
||||
|
||||
js_library(
|
||||
name = PKG_DIRNAME,
|
||||
srcs = NPM_MODULE_EXTRA_FILES,
|
||||
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
|
||||
package_name = PKG_REQUIRE_NAME,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
pkg_npm(
|
||||
name = "npm_module",
|
||||
deps = [":" + PKG_DIRNAME],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "build",
|
||||
srcs = [":npm_module"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
pkg_npm_types(
|
||||
name = "npm_module_types",
|
||||
srcs = SRCS,
|
||||
deps = [":tsc_types"],
|
||||
package_name = PKG_REQUIRE_NAME,
|
||||
tsconfig = ":tsconfig",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "build_types",
|
||||
srcs = [":npm_module_types"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
23
packages/home/sample_data_card/README.mdx
Normal file
23
packages/home/sample_data_card/README.mdx
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: home/SampleData/Cards
|
||||
slug: /home/sample-data/cards
|
||||
title: Sample Data Cards
|
||||
summary: A component that displays Sample Data Sets as cards and grid of cards.
|
||||
tags: ['home', 'component', 'sample-data']
|
||||
date: 2022-06-30
|
||||
---
|
||||
|
||||
This package contains a pair of components. The first displays a Sample Data Set as a card which displays the Sample Data Set's name, description, and image as well as functions to install, uninstall and navigate to Saved Objects associated with the data set. The other component fetches a list of Sample Data Sets and displays them as a grid, which also responds to install and uninstall events.
|
||||
|
||||
## API
|
||||
|
||||
| Export | Description |
|
||||
|---|---|
|
||||
| `SampleDataCard` | Fetches and displays a grid of Sample Data Sets as `SampleDataCard` components. |
|
||||
| `SampleDataCard` | A card component representing a Sample Data Set, which can install, uninstall and navigate relevant objects from a Sample Data Set. |
|
||||
| `SampleDataCardProvider` | Provides contextual services to `KibanaNoDataPage`. |
|
||||
| `SampleDataCardKibanaProvider` | Maps Kibana dependencies to provide contextual services to `KibanaNoDataPage`. |
|
||||
|
||||
## EUI Promotion Status
|
||||
|
||||
This component is not currently considered for promotion to EUI.
|
13
packages/home/sample_data_card/jest.config.js
Normal file
13
packages/home/sample_data_card/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/packages/home/sample_data_card'],
|
||||
};
|
8
packages/home/sample_data_card/package.json
Normal file
8
packages/home/sample_data_card/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@kbn/home-sample-data-card",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "./target_node/index.js",
|
||||
"browser": "./target_web/index.js",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
275
packages/home/sample_data_card/src/__snapshots__/sample_data_card.test.tsx.snap
generated
Normal file
275
packages/home/sample_data_card/src/__snapshots__/sample_data_card.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,275 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SampleDataCard installed renders with app links 1`] = `
|
||||
<div
|
||||
class="euiPanel euiPanel--plain euiPanel--paddingMedium euiCard euiCard--leftAligned euiCard--hasBetaBadge emotion-euiPanel-grow-m-m-plain-hasShadow"
|
||||
data-test-subj="sampleDataSetCardsample-data-set"
|
||||
>
|
||||
<div
|
||||
class="euiCard__top"
|
||||
>
|
||||
<div
|
||||
class="euiCard__image"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
src="test-file-stub"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiCard__content"
|
||||
>
|
||||
<span
|
||||
class="euiTitle euiCard__title emotion-euiTitle-s"
|
||||
id="generated-idTitle"
|
||||
>
|
||||
Sample Data Set
|
||||
</span>
|
||||
<div
|
||||
class="euiText euiCard__description emotion-euiText-s"
|
||||
id="generated-idDescription"
|
||||
>
|
||||
<p>
|
||||
This is a sample data set you can use.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="euiCard__betaBadgeWrapper"
|
||||
>
|
||||
<span
|
||||
class="euiBetaBadge euiBetaBadge--hollow euiCard__betaBadge"
|
||||
id="generated-idBetaBadge"
|
||||
title="installed"
|
||||
>
|
||||
installed
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="euiCard__footer"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Remove Sample Data Set"
|
||||
class="euiButtonEmpty euiButtonEmpty--danger euiButtonEmpty--flushLeft"
|
||||
data-test-subj="removeSampleDataSetsample-data-set"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Remove
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownCenter"
|
||||
data-test-subj="launchSampleDataSetsample-data-set"
|
||||
id="sampleDataLinkssample-data-set"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="View Sample Data Set"
|
||||
class="euiButton euiButton--primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
/>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
View data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SampleDataCard installed renders without app links 1`] = `
|
||||
<div
|
||||
class="euiPanel euiPanel--plain euiPanel--paddingMedium euiCard euiCard--leftAligned euiCard--hasBetaBadge emotion-euiPanel-grow-m-m-plain-hasShadow"
|
||||
data-test-subj="sampleDataSetCardsample-data-set"
|
||||
>
|
||||
<div
|
||||
class="euiCard__top"
|
||||
>
|
||||
<div
|
||||
class="euiCard__image"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
src="test-file-stub"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiCard__content"
|
||||
>
|
||||
<span
|
||||
class="euiTitle euiCard__title emotion-euiTitle-s"
|
||||
id="generated-idTitle"
|
||||
>
|
||||
Sample Data Set
|
||||
</span>
|
||||
<div
|
||||
class="euiText euiCard__description emotion-euiText-s"
|
||||
id="generated-idDescription"
|
||||
>
|
||||
<p>
|
||||
This is a sample data set you can use.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="euiCard__betaBadgeWrapper"
|
||||
>
|
||||
<span
|
||||
class="euiBetaBadge euiBetaBadge--hollow euiCard__betaBadge"
|
||||
id="generated-idBetaBadge"
|
||||
title="installed"
|
||||
>
|
||||
installed
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class="euiCard__footer"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Remove Sample Data Set"
|
||||
class="euiButtonEmpty euiButtonEmpty--danger euiButtonEmpty--flushLeft"
|
||||
data-test-subj="removeSampleDataSetsample-data-set"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Remove
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="View Sample Data Set"
|
||||
class="euiButton euiButton--primary"
|
||||
data-test-subj="launchSampleDataSetsample-data-set"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
View data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SampleDataCard not installed renders 1`] = `
|
||||
<div
|
||||
class="euiPanel euiPanel--plain euiPanel--paddingMedium euiCard euiCard--leftAligned emotion-euiPanel-grow-m-m-plain-hasShadow"
|
||||
data-test-subj="sampleDataSetCardsample-data-set"
|
||||
>
|
||||
<div
|
||||
class="euiCard__top"
|
||||
>
|
||||
<div
|
||||
class="euiCard__image"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
src="test-file-stub"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiCard__content"
|
||||
>
|
||||
<span
|
||||
class="euiTitle euiCard__title emotion-euiTitle-s"
|
||||
id="generated-idTitle"
|
||||
>
|
||||
Sample Data Set
|
||||
</span>
|
||||
<div
|
||||
class="euiText euiCard__description emotion-euiText-s"
|
||||
id="generated-idDescription"
|
||||
>
|
||||
<p>
|
||||
This is a sample data set you can use.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiCard__footer"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentFlexEnd euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Add Sample Data Set"
|
||||
class="euiButton euiButton--primary"
|
||||
data-test-subj="addSampleDataSetsample-data-set"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Add data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
22
packages/home/sample_data_card/src/constants.ts
Normal file
22
packages/home/sample_data_card/src/constants.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DataSetStatusType for an installed data set.
|
||||
* @see src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types
|
||||
*/
|
||||
export const INSTALLED_STATUS = 'installed';
|
||||
|
||||
/**
|
||||
* DataSetStatusType for a data set that is not installed yet.
|
||||
* @see src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types
|
||||
*/
|
||||
export const UNINSTALLED_STATUS = 'not_installed';
|
||||
|
||||
// Corresponds to src/plugins/home/server/services/sample_data/routes
|
||||
export const SAMPLE_DATA_API = '/api/sample_data';
|
33
packages/home/sample_data_card/src/footer/__snapshots__/disabled_footer.test.tsx.snap
generated
Normal file
33
packages/home/sample_data_card/src/footer/__snapshots__/disabled_footer.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`install footer should render 1`] = `
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentFlexEnd euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Add Data Set Name"
|
||||
class="euiButton euiButton--primary euiButton-isDisabled"
|
||||
data-test-subj="addSampleDataSetdata-set-id"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Add data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
28
packages/home/sample_data_card/src/footer/__snapshots__/install_footer.test.tsx.snap
generated
Normal file
28
packages/home/sample_data_card/src/footer/__snapshots__/install_footer.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`install footer should render 1`] = `
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentFlexEnd euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Add Data Set Name"
|
||||
class="euiButton euiButton--primary"
|
||||
data-test-subj="addSampleDataSetdata-set-id"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Add data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
48
packages/home/sample_data_card/src/footer/__snapshots__/remove_footer.test.tsx.snap
generated
Normal file
48
packages/home/sample_data_card/src/footer/__snapshots__/remove_footer.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`install footer should render 1`] = `
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Remove Data Set Name"
|
||||
class="euiButtonEmpty euiButtonEmpty--danger euiButtonEmpty--flushLeft"
|
||||
data-test-subj="removeSampleDataSetdata-set-id"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Remove
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="View Data Set Name"
|
||||
class="euiButton euiButton--primary"
|
||||
data-test-subj="launchSampleDataSetdata-set-id"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
View data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
86
packages/home/sample_data_card/src/footer/__snapshots__/view_button.test.tsx.snap
generated
Normal file
86
packages/home/sample_data_card/src/footer/__snapshots__/view_button.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should render popover when appLinks is not empty 1`] = `
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownCenter"
|
||||
data-test-subj="launchSampleDataSetecommerce"
|
||||
id="sampleDataLinksecommerce"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="View Sample eCommerce orders"
|
||||
class="euiButton euiButton--primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
/>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
View data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render popover with ordered appLinks 1`] = `
|
||||
<div
|
||||
class="euiPopover euiPopover--anchorDownCenter"
|
||||
data-test-subj="launchSampleDataSetecommerce"
|
||||
id="sampleDataLinksecommerce"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="View Sample eCommerce orders"
|
||||
class="euiButton euiButton--primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
/>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
View data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render simple button when appLinks is empty 1`] = `
|
||||
<button
|
||||
aria-label="View Sample eCommerce orders"
|
||||
class="euiButton euiButton--primary"
|
||||
data-test-subj="launchSampleDataSetecommerce"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
View data
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { DisabledFooter, Props } from './disabled_footer';
|
||||
import { SampleDataCardProvider } from '../services';
|
||||
import { getMockServices } from '../mocks';
|
||||
|
||||
describe('install footer', () => {
|
||||
const props: Props = {
|
||||
id: 'data-set-id',
|
||||
name: 'Data Set Name',
|
||||
statusMsg: 'Data Set Status Message',
|
||||
};
|
||||
|
||||
const render = (element: React.ReactElement) =>
|
||||
renderWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices()}>{element}</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
test('should render', () => {
|
||||
const component = render(<DisabledFooter {...props} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
|
||||
/**
|
||||
* Props for the `DisabledFooter` component.
|
||||
*/
|
||||
export type Props = Pick<SampleDataSet, 'id' | 'name' | 'statusMsg'>;
|
||||
|
||||
const addDataLabel = i18n.translate('homePackages.sampleDataCard.default.addButtonLabel', {
|
||||
defaultMessage: 'Add data',
|
||||
});
|
||||
|
||||
/**
|
||||
* A footer for the `SampleDataCard` displayed when an unknown error or status prevents a person
|
||||
* from installing the Sample Data Set.
|
||||
*/
|
||||
export const DisabledFooter = ({ id, name, statusMsg }: Props) => {
|
||||
const errorMessage = i18n.translate(
|
||||
'homePackages.sampleDataCard.default.unableToVerifyErrorMessage',
|
||||
{ defaultMessage: 'Unable to verify dataset status, error: {statusMsg}', values: { statusMsg } }
|
||||
);
|
||||
|
||||
const addButtonAriaLabel = i18n.translate(
|
||||
'homePackages.sampleDataCard.default.addButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Add {datasetName}',
|
||||
values: {
|
||||
datasetName: name,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip position="top" content={<p>{errorMessage}</p>}>
|
||||
<EuiButton
|
||||
isDisabled
|
||||
data-test-subj={`addSampleDataSet${id}`}
|
||||
aria-label={addButtonAriaLabel}
|
||||
>
|
||||
{addDataLabel}
|
||||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
49
packages/home/sample_data_card/src/footer/footer.stories.tsx
Normal file
49
packages/home/sample_data_card/src/footer/footer.stories.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { Params, getStoryArgTypes, getStoryServices, mockDataSet } from '../mocks';
|
||||
import { SampleDataCardProvider } from '../services';
|
||||
import { Footer as Component } from '.';
|
||||
|
||||
import mdx from '../../README.mdx';
|
||||
|
||||
export default {
|
||||
title: 'Sample Data/Card Footer',
|
||||
description: '',
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
decorators: [(Story) => <div style={{ width: '433px', padding: '25px' }}>{Story()}</div>],
|
||||
} as ComponentMeta<typeof Component>;
|
||||
|
||||
const { description, ...argTypes } = getStoryArgTypes();
|
||||
|
||||
export const CardFooter = (params: Params) => {
|
||||
const { includeAppLinks, status, ...rest } = params;
|
||||
const sampleDataSet: SampleDataSet = {
|
||||
...mockDataSet,
|
||||
...rest,
|
||||
status,
|
||||
appLinks: includeAppLinks ? mockDataSet.appLinks : [],
|
||||
};
|
||||
|
||||
return (
|
||||
<SampleDataCardProvider {...getStoryServices(params)}>
|
||||
<Component sampleDataSet={sampleDataSet} onAction={action('onAction')} />
|
||||
</SampleDataCardProvider>
|
||||
);
|
||||
};
|
||||
|
||||
CardFooter.argTypes = argTypes;
|
40
packages/home/sample_data_card/src/footer/index.tsx
Normal file
40
packages/home/sample_data_card/src/footer/index.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { SampleDataSet, InstalledStatus } from '@kbn/home-sample-data-types';
|
||||
import { INSTALLED_STATUS, UNINSTALLED_STATUS } from '../constants';
|
||||
|
||||
import { DisabledFooter } from './disabled_footer';
|
||||
import { InstallFooter } from './install_footer';
|
||||
import { RemoveFooter } from './remove_footer';
|
||||
|
||||
/**
|
||||
* Props for the `Footer` component.
|
||||
*/
|
||||
export interface Props {
|
||||
/** The Sample Data Set and its status. */
|
||||
sampleDataSet: SampleDataSet;
|
||||
/** The handler to invoke when an action is performed upon the Sample Data Set. */
|
||||
onAction: (id: string, status: InstalledStatus) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the appropriate Footer component based on the status of the Sample Data Set.
|
||||
*/
|
||||
export const Footer = ({ sampleDataSet, onAction }: Props) => {
|
||||
if (sampleDataSet.status === INSTALLED_STATUS) {
|
||||
return <RemoveFooter onRemove={(id) => onAction(id, UNINSTALLED_STATUS)} {...sampleDataSet} />;
|
||||
}
|
||||
|
||||
if (sampleDataSet.status === UNINSTALLED_STATUS) {
|
||||
return <InstallFooter onInstall={(id) => onAction(id, INSTALLED_STATUS)} {...sampleDataSet} />;
|
||||
}
|
||||
|
||||
return <DisabledFooter {...sampleDataSet} />;
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderWithIntl, mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { InstallFooter, Props } from './install_footer';
|
||||
import { SampleDataCardProvider, Services } from '../services';
|
||||
import { getMockServices } from '../mocks';
|
||||
|
||||
describe('install footer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const id = 'data-set-id';
|
||||
const onInstall = jest.fn();
|
||||
const notifyError = jest.fn();
|
||||
const notifySuccess = jest.fn();
|
||||
|
||||
const props: Props = {
|
||||
id,
|
||||
onInstall,
|
||||
name: 'Data Set Name',
|
||||
defaultIndex: 'default-index',
|
||||
};
|
||||
|
||||
const render = (element: React.ReactElement) =>
|
||||
renderWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices()}>{element}</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
const mount = (element: React.ReactElement, params?: Partial<Services>) =>
|
||||
mountWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices({ notifyError, notifySuccess, ...params })}>
|
||||
{element}
|
||||
</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
test('should render', () => {
|
||||
const component = render(<InstallFooter {...props} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should invoke onInstall when install button is clicked', async () => {
|
||||
const component = mount(<InstallFooter {...props} />);
|
||||
|
||||
await act(async () => {
|
||||
component.find(`button[data-test-subj="addSampleDataSet${id}"]`).simulate('click');
|
||||
});
|
||||
|
||||
expect(onInstall).toHaveBeenCalledTimes(1);
|
||||
expect(notifySuccess).toHaveBeenCalledTimes(1);
|
||||
expect(notifyError).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('should not invoke onInstall when install button is clicked and an error is thrown', async () => {
|
||||
const component = mount(<InstallFooter {...props} />, {
|
||||
installSampleDataSet: () => {
|
||||
throw new Error('error');
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
component.find(`button[data-test-subj="addSampleDataSet${id}"]`).simulate('click');
|
||||
});
|
||||
|
||||
expect(onInstall).toHaveBeenCalledTimes(0);
|
||||
expect(notifySuccess).toHaveBeenCalledTimes(0);
|
||||
expect(notifyError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
65
packages/home/sample_data_card/src/footer/install_footer.tsx
Normal file
65
packages/home/sample_data_card/src/footer/install_footer.tsx
Normal file
|
@ -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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { useInstall } from '../hooks';
|
||||
import type { UseInstallParams } from '../hooks';
|
||||
|
||||
/**
|
||||
* Props for the `InstallFooter` component.
|
||||
*/
|
||||
export type Props = Pick<SampleDataSet, 'id' | 'name'> & UseInstallParams;
|
||||
|
||||
const addingLabel = i18n.translate('homePackages.sampleDataCard.addingButtonLabel', {
|
||||
defaultMessage: 'Adding',
|
||||
});
|
||||
|
||||
const addLabel = i18n.translate('homePackages.sampleDataCard.addButtonLabel', {
|
||||
defaultMessage: 'Add data',
|
||||
});
|
||||
|
||||
/**
|
||||
* A footer displayed when a Sample Data Set is not installed, allowing a person to install it.
|
||||
*/
|
||||
export const InstallFooter = (params: Props) => {
|
||||
const [install, isInstalling] = useInstall(params);
|
||||
const { id, name } = params;
|
||||
|
||||
const addingAriaLabel = i18n.translate('homePackages.sampleDataCard.addingButtonAriaLabel', {
|
||||
defaultMessage: 'Adding {datasetName}',
|
||||
values: {
|
||||
datasetName: name,
|
||||
},
|
||||
});
|
||||
|
||||
const addAriaLabel = i18n.translate('homePackages.sampleDataCard.addButtonAriaLabel', {
|
||||
defaultMessage: 'Add {datasetName}',
|
||||
values: {
|
||||
datasetName: name,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={isInstalling}
|
||||
onClick={install}
|
||||
data-test-subj={`addSampleDataSet${id}`}
|
||||
aria-label={isInstalling ? addingAriaLabel : addAriaLabel}
|
||||
>
|
||||
{isInstalling ? addingLabel : addLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderWithIntl, mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { RemoveFooter, Props } from './remove_footer';
|
||||
import { SampleDataCardProvider, Services } from '../services';
|
||||
import { getMockServices } from '../mocks';
|
||||
|
||||
describe('install footer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const id = 'data-set-id';
|
||||
const onRemove = jest.fn();
|
||||
const notifyError = jest.fn();
|
||||
const notifySuccess = jest.fn();
|
||||
|
||||
const props: Props = {
|
||||
id,
|
||||
onRemove,
|
||||
name: 'Data Set Name',
|
||||
defaultIndex: 'default-index',
|
||||
overviewDashboard: 'path/to/overview',
|
||||
appLinks: [],
|
||||
};
|
||||
|
||||
const render = (element: React.ReactElement) =>
|
||||
renderWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices()}>{element}</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
const mount = (element: React.ReactElement, params?: Partial<Services>) =>
|
||||
mountWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices({ notifyError, notifySuccess, ...params })}>
|
||||
{element}
|
||||
</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
test('should render', () => {
|
||||
const component = render(<RemoveFooter {...props} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should invoke onRemove when remove button is clicked', async () => {
|
||||
const component = mount(<RemoveFooter {...props} />);
|
||||
|
||||
await act(async () => {
|
||||
component.find(`button[data-test-subj="removeSampleDataSet${id}"]`).simulate('click');
|
||||
});
|
||||
|
||||
expect(onRemove).toHaveBeenCalledTimes(1);
|
||||
expect(notifySuccess).toHaveBeenCalledTimes(1);
|
||||
expect(notifyError).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('should not invoke onRemove when remove button is clicked and an error is thrown', async () => {
|
||||
const component = mount(<RemoveFooter {...props} />, {
|
||||
removeSampleDataSet: () => {
|
||||
throw new Error('error');
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
component.find(`button[data-test-subj="removeSampleDataSet${id}"]`).simulate('click');
|
||||
});
|
||||
|
||||
expect(onRemove).toHaveBeenCalledTimes(0);
|
||||
expect(notifySuccess).toHaveBeenCalledTimes(0);
|
||||
expect(notifyError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
72
packages/home/sample_data_card/src/footer/remove_footer.tsx
Normal file
72
packages/home/sample_data_card/src/footer/remove_footer.tsx
Normal file
|
@ -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 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { useRemove } from '../hooks';
|
||||
import { ViewButton } from './view_button';
|
||||
import type { UseRemoveParams } from '../hooks';
|
||||
import type { Props as ViewButtonProps } from './view_button';
|
||||
|
||||
/**
|
||||
* Props for the `RemoveFooter` component.
|
||||
*/
|
||||
export type Props = Pick<SampleDataSet, 'id' | 'name'> & UseRemoveParams & ViewButtonProps;
|
||||
|
||||
const removeLabel = i18n.translate('homePackages.sampleDataCard.removeButtonLabel', {
|
||||
defaultMessage: 'Remove',
|
||||
});
|
||||
|
||||
const removingLabel = i18n.translate('homePackages.sampleDataCard.removingButtonLabel', {
|
||||
defaultMessage: 'Removing',
|
||||
});
|
||||
|
||||
/**
|
||||
* A footer displayed when a Sample Data Set is installed, allowing a person to remove it or view
|
||||
* saved objects associated with it in their related solutions.
|
||||
*/
|
||||
export const RemoveFooter = (props: Props) => {
|
||||
const [remove, isRemoving] = useRemove(props);
|
||||
const { id, name } = props;
|
||||
|
||||
const removeAriaLabel = i18n.translate('homePackages.sampleDataCard.removeButtonAriaLabel', {
|
||||
defaultMessage: 'Remove {datasetName}',
|
||||
values: {
|
||||
datasetName: name,
|
||||
},
|
||||
});
|
||||
|
||||
const removingAriaLabel = i18n.translate('homePackages.sampleDataCard.removingButtonAriaLabel', {
|
||||
defaultMessage: 'Removing {datasetName}',
|
||||
values: {
|
||||
datasetName: name,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
isLoading={isRemoving}
|
||||
onClick={remove}
|
||||
color="danger"
|
||||
data-test-subj={`removeSampleDataSet${id}`}
|
||||
flush="left"
|
||||
aria-label={isRemoving ? removingAriaLabel : removeAriaLabel}
|
||||
>
|
||||
{isRemoving ? removingLabel : removeLabel}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ViewButton {...props} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { ViewButton } from './view_button';
|
||||
import { SampleDataCardProvider } from '../services';
|
||||
import { getMockServices } from '../mocks';
|
||||
|
||||
const render = (element: React.ReactElement) =>
|
||||
renderWithIntl(<SampleDataCardProvider {...getMockServices()}>{element}</SampleDataCardProvider>);
|
||||
|
||||
test('should render simple button when appLinks is empty', () => {
|
||||
const component = render(
|
||||
<ViewButton
|
||||
id="ecommerce"
|
||||
name="Sample eCommerce orders"
|
||||
overviewDashboard="722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
appLinks={[]}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render popover when appLinks is not empty', () => {
|
||||
const appLinks = [
|
||||
{
|
||||
path: 'app/myAppPath',
|
||||
label: 'myAppLabel',
|
||||
icon: 'logoKibana',
|
||||
},
|
||||
];
|
||||
|
||||
const component = render(
|
||||
<ViewButton
|
||||
id="ecommerce"
|
||||
name="Sample eCommerce orders"
|
||||
overviewDashboard="722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
appLinks={appLinks}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render popover with ordered appLinks', () => {
|
||||
const appLinks = [
|
||||
{
|
||||
path: 'app/myAppPath',
|
||||
label: 'myAppLabel[-1]',
|
||||
icon: 'logoKibana',
|
||||
order: -1, // to position it above Dashboard link
|
||||
},
|
||||
{
|
||||
path: 'app/myAppPath',
|
||||
label: 'myAppLabel',
|
||||
icon: 'logoKibana',
|
||||
},
|
||||
{
|
||||
path: 'app/myAppPath',
|
||||
label: 'myAppLabel[5]',
|
||||
icon: 'logoKibana',
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
path: 'app/myAppPath',
|
||||
label: 'myAppLabel[3]',
|
||||
icon: 'logoKibana',
|
||||
order: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const component = render(
|
||||
<ViewButton
|
||||
id="ecommerce"
|
||||
name="Sample eCommerce orders"
|
||||
overviewDashboard="722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
appLinks={appLinks}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
122
packages/home/sample_data_card/src/footer/view_button.tsx
Normal file
122
packages/home/sample_data_card/src/footer/view_button.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiContextMenu,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiIcon,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { useServices } from '../services';
|
||||
|
||||
/**
|
||||
* Props for the `ViewButton` component.
|
||||
*/
|
||||
export type Props = Pick<SampleDataSet, 'id' | 'name' | 'overviewDashboard' | 'appLinks'>;
|
||||
|
||||
const viewDataButtonLabel = i18n.translate('homePackages.sampleDataCard.viewDataButtonLabel', {
|
||||
defaultMessage: 'View data',
|
||||
});
|
||||
|
||||
const dashboardLabel = i18n.translate('homePackages.sampleDataCard.dashboardLinkLabel', {
|
||||
defaultMessage: 'Dashboard',
|
||||
});
|
||||
|
||||
/**
|
||||
* A button displayed when a Sample Data Set is installed, allowing a person to view the overview dashboard,
|
||||
* and, if included, a number of actions to navigate to other solutions.
|
||||
*/
|
||||
export const ViewButton = ({ id, name, overviewDashboard, appLinks }: Props) => {
|
||||
const { addBasePath, getAppNavigationHandler } = useServices();
|
||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||
|
||||
const viewDataButtonAriaLabel = i18n.translate(
|
||||
'homePackages.sampleDataCard.viewDataButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View {datasetName}',
|
||||
values: {
|
||||
datasetName: name,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const dashboardPath = `/app/dashboards#/view/${overviewDashboard}`;
|
||||
|
||||
if (appLinks.length === 0) {
|
||||
return (
|
||||
<EuiButton
|
||||
onClick={getAppNavigationHandler(dashboardPath)}
|
||||
data-test-subj={`launchSampleDataSet${id}`}
|
||||
aria-label={viewDataButtonAriaLabel}
|
||||
>
|
||||
{viewDataButtonLabel}
|
||||
</EuiButton>
|
||||
);
|
||||
}
|
||||
|
||||
const togglePopover = () => {
|
||||
setIsPopoverOpen(!isPopoverOpen);
|
||||
};
|
||||
|
||||
const dashboardAppLink = {
|
||||
path: dashboardPath,
|
||||
label: dashboardLabel,
|
||||
icon: 'dashboardApp',
|
||||
order: 0,
|
||||
'data-test-subj': `viewSampleDataSet${id}-dashboard`,
|
||||
};
|
||||
|
||||
const sortedItems = sortBy([dashboardAppLink, ...appLinks], 'order');
|
||||
const items = sortedItems.map(({ path, label, icon, ...rest }) => {
|
||||
return {
|
||||
name: label,
|
||||
icon: <EuiIcon type={icon} size="m" />,
|
||||
href: addBasePath(path),
|
||||
onClick: getAppNavigationHandler(path),
|
||||
...(rest['data-test-subj'] ? { 'data-test-subj': rest['data-test-subj'] } : {}),
|
||||
};
|
||||
});
|
||||
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
items,
|
||||
},
|
||||
];
|
||||
|
||||
const popoverButton = (
|
||||
<EuiButton
|
||||
aria-label={viewDataButtonAriaLabel}
|
||||
onClick={togglePopover}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
>
|
||||
{viewDataButtonLabel}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id={`sampleDataLinks${id}`}
|
||||
button={popoverButton}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downCenter"
|
||||
data-test-subj={`launchSampleDataSet${id}`}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
13
packages/home/sample_data_card/src/hooks/index.ts
Normal file
13
packages/home/sample_data_card/src/hooks/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { useInstall } from './use_install';
|
||||
export type { Params as UseInstallParams } from './use_install';
|
||||
|
||||
export { useRemove } from './use_remove';
|
||||
export type { Params as UseRemoveParams } from './use_remove';
|
64
packages/home/sample_data_card/src/hooks/use_install.ts
Normal file
64
packages/home/sample_data_card/src/hooks/use_install.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { useServices } from '../services';
|
||||
|
||||
/**
|
||||
* Parameters for the `useInstall` React hook.
|
||||
*/
|
||||
export type Params = Pick<SampleDataSet, 'id' | 'defaultIndex' | 'name'> & {
|
||||
/** Handler to invoke when the Sample Data Set is successfully installed. */
|
||||
onInstall: (id: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A React hook that allows a component to install a sample data set, handling success and
|
||||
* failure in the Kibana UI. It also provides a boolean that indicates if the data set is
|
||||
* in the process of being installed.
|
||||
*/
|
||||
export const useInstall = ({
|
||||
id,
|
||||
defaultIndex,
|
||||
name,
|
||||
onInstall,
|
||||
}: Params): [() => void, boolean] => {
|
||||
const { installSampleDataSet, notifyError, notifySuccess } = useServices();
|
||||
const [isInstalling, setIsInstalling] = React.useState(false);
|
||||
|
||||
const install = useCallback(async () => {
|
||||
try {
|
||||
setIsInstalling(true);
|
||||
await installSampleDataSet(id, defaultIndex);
|
||||
setIsInstalling(false);
|
||||
|
||||
notifySuccess({
|
||||
title: i18n.translate('homePackages.sampleDataSet.installedLabel', {
|
||||
defaultMessage: '{name} installed',
|
||||
values: { name },
|
||||
}),
|
||||
['data-test-subj']: 'sampleDataSetInstallToast',
|
||||
});
|
||||
onInstall(id);
|
||||
} catch (e) {
|
||||
setIsInstalling(false);
|
||||
notifyError({
|
||||
title: i18n.translate('homePackages.sampleDataSet.unableToInstallErrorMessage', {
|
||||
defaultMessage: 'Unable to install sample data set: {name}',
|
||||
values: { name },
|
||||
}),
|
||||
text: `${e.message}`,
|
||||
});
|
||||
}
|
||||
}, [installSampleDataSet, notifyError, notifySuccess, id, defaultIndex, name, onInstall]);
|
||||
|
||||
return [install, isInstalling];
|
||||
};
|
61
packages/home/sample_data_card/src/hooks/use_remove.ts
Normal file
61
packages/home/sample_data_card/src/hooks/use_remove.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { useServices } from '../services';
|
||||
|
||||
/**
|
||||
* Parameters for the `useRemove` React hook.
|
||||
*/
|
||||
export type Params = Pick<SampleDataSet, 'id' | 'defaultIndex' | 'name'> & {
|
||||
/** Handler to invoke when the Sample Data Set is successfully removed. */
|
||||
onRemove: (id: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A React hook that allows a component to remove a sample data set, handling success and
|
||||
* failure in the Kibana UI. It also provides a boolean that indicates if the data set is
|
||||
* in the process of being removed.
|
||||
*/
|
||||
export const useRemove = ({ id, defaultIndex, name, onRemove }: Params): [() => void, boolean] => {
|
||||
const { removeSampleDataSet, notifyError, notifySuccess } = useServices();
|
||||
const [isRemoving, setIsRemoving] = React.useState(false);
|
||||
|
||||
const remove = useCallback(async () => {
|
||||
try {
|
||||
setIsRemoving(true);
|
||||
await removeSampleDataSet(id, defaultIndex);
|
||||
setIsRemoving(false);
|
||||
|
||||
notifySuccess({
|
||||
title: i18n.translate('homePackages.sampleDataSet.uninstalledLabel', {
|
||||
defaultMessage: '{name} uninstalled',
|
||||
values: { name },
|
||||
}),
|
||||
['data-test-subj']: 'sampleDataSetUninstallToast',
|
||||
});
|
||||
|
||||
onRemove(id);
|
||||
} catch (e) {
|
||||
setIsRemoving(false);
|
||||
|
||||
notifyError({
|
||||
title: i18n.translate('homePackages.sampleDataSet.unableToUninstallErrorMessage', {
|
||||
defaultMessage: 'Unable to uninstall sample data set: {name}',
|
||||
values: { name },
|
||||
}),
|
||||
text: `${e.message}`,
|
||||
});
|
||||
}
|
||||
}, [removeSampleDataSet, notifyError, notifySuccess, id, defaultIndex, name, onRemove]);
|
||||
|
||||
return [remove, isRemoving];
|
||||
};
|
24
packages/home/sample_data_card/src/index.ts
Normal file
24
packages/home/sample_data_card/src/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { SampleDataCard } from './sample_data_card';
|
||||
export type { Props as SampleDataCardProps } from './sample_data_card';
|
||||
|
||||
export { SampleDataCardKibanaProvider, SampleDataCardProvider } from './services';
|
||||
export type {
|
||||
Services as SampleDataCardServices,
|
||||
KibanaDependencies as SampleDataCardKibanaDependencies,
|
||||
} from './services';
|
||||
|
||||
// TODO: clintandrewhall - convert to new Storybook mock when published.
|
||||
export {
|
||||
getStoryArgTypes as getSampleDataCardStoryArgTypes,
|
||||
getStoryServices as getSampleDataCardStoryServices,
|
||||
getMockDataSet as getSampleDataCardMockDataSet,
|
||||
} from './mocks';
|
||||
export type { Params as SampleDataCardStorybookParams } from './mocks';
|
BIN
packages/home/sample_data_card/src/mocks/dashboard.png
Normal file
BIN
packages/home/sample_data_card/src/mocks/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
BIN
packages/home/sample_data_card/src/mocks/dashboard_dark.png
Normal file
BIN
packages/home/sample_data_card/src/mocks/dashboard_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
6
packages/home/sample_data_card/src/mocks/icon.svg
Normal file
6
packages/home/sample_data_card/src/mocks/icon.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="34" height="32" viewBox="0 0 34 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.00024 22C2.47508 22.2862 3.49851 22.5776 4.42785 22.8421L4.64995 22.9054C7.06615 23.5957 10.4115 24 14 24C14.7424 24 15.4744 23.9827 16.1899 23.9491C16.3251 24.6338 16.5303 25.2934 16.7976 25.9201C15.8936 25.9725 14.9581 26 14 26C6.26801 26 0 24.2091 0 22V4C0 1.79086 6.26801 0 14 0C21.732 0 28 1.79086 28 4V12.2C27.3538 12.0689 26.6849 12 26 12C24.0582 12 22.2456 12.5535 20.7115 13.5113C18.7188 13.8229 16.4318 14 14 14C8.90735 14 4.44979 13.2231 2 12.0615V16C2.47483 16.2862 3.49851 16.5776 4.42785 16.8421L4.64995 16.9054C7.06615 17.5957 10.4115 18 14 18C14.9798 18 15.9415 17.9699 16.871 17.912C16.5813 18.558 16.3581 19.2404 16.2102 19.9504C15.4903 19.9831 14.7521 20 14 20C8.90735 20 4.44979 19.2231 2 18.0615L2.00024 22ZM2 6.06148V10C2.47483 10.2862 3.49853 10.5776 4.42787 10.8421L4.64995 10.9054C7.06615 11.5957 10.4115 12 14 12C17.5885 12 20.9338 11.5957 23.3501 10.9054L23.5863 10.8381C24.5124 10.5746 25.5311 10.2848 26.0035 10H26V6.06148C23.5502 7.2231 19.0927 8 14 8C8.90735 8 4.44979 7.2231 2 6.06148ZM23.3501 3.09462C20.9338 2.40428 17.5885 2 14 2C10.4115 2 7.06615 2.40428 4.64995 3.09462C3.6667 3.37555 2.89023 3.69073 2.37721 4C2.89023 4.30927 3.6667 4.62445 4.64995 4.90538C7.06615 5.59572 10.4115 6 14 6C17.5885 6 20.9338 5.59572 23.3501 4.90538C24.3333 4.62445 25.1098 4.30927 25.6228 4C25.1098 3.69073 24.3333 3.37555 23.3501 3.09462Z" fill="#343741"/>
|
||||
<path d="M22 28.5C22 27.675 22.675 27 23.5 27C24.325 27 25 27.675 25 28.5C25 29.325 24.325 30 23.5 30C22.675 30 22 29.325 22 28.5Z" fill="#343741"/>
|
||||
<path d="M19 16.5V15H21.475L22.15 16.5H33.25C33.7 16.5 34 16.8 34 17.25C34 17.4 34 17.475 33.85 17.625L31.15 22.5C30.925 22.95 30.475 23.25 29.875 23.25H24.325L23.65 24.525V24.6C23.65 24.675 23.725 24.75 23.8 24.75H32.5V26.25H23.5C22.675 26.25 22 25.575 22 24.75C22 24.525 22.075 24.225 22.15 24L23.2 22.2L20.5 16.5H19Z" fill="#343741"/>
|
||||
<path d="M29.5 28.5C29.5 27.675 30.175 27 31 27C31.825 27 32.5 27.675 32.5 28.5C32.5 29.325 31.825 30 31 30C30.175 30 29.5 29.325 29.5 28.5Z" fill="#343741"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
137
packages/home/sample_data_card/src/mocks/index.ts
Normal file
137
packages/home/sample_data_card/src/mocks/index.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import previewImagePath from './dashboard.png';
|
||||
import darkPreviewImagePath from './dashboard_dark.png';
|
||||
import iconPath from './icon.svg';
|
||||
|
||||
import { Services } from '../services';
|
||||
|
||||
/**
|
||||
* A set of e-commerce images for use in Storybook stories.
|
||||
*/
|
||||
export const ecommerceImages = { previewImagePath, darkPreviewImagePath, iconPath };
|
||||
|
||||
/**
|
||||
* A mocked sample data set for use in Storybook stories.
|
||||
*/
|
||||
export const mockDataSet: SampleDataSet = {
|
||||
darkPreviewImagePath,
|
||||
defaultIndex: 'default-index',
|
||||
iconPath,
|
||||
id: 'sample-data-set',
|
||||
overviewDashboard: 'overview-dashboard',
|
||||
previewImagePath,
|
||||
appLinks: [
|
||||
{
|
||||
icon: 'visLine',
|
||||
label: 'View in App',
|
||||
path: 'path-to-app',
|
||||
},
|
||||
],
|
||||
name: 'Sample Data Set',
|
||||
description: 'This is a sample data set you can use.',
|
||||
status: 'not_installed',
|
||||
statusMsg: 'optional status message',
|
||||
};
|
||||
|
||||
/**
|
||||
* Customize the Sample Data Set mock.
|
||||
*/
|
||||
export const getMockDataSet = (params: Partial<SampleDataSet> = {}) => ({
|
||||
...mockDataSet,
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* Parameters drawn from the Storybook arguments collection that customize a component story.
|
||||
*/
|
||||
export type Params = Record<keyof ReturnType<typeof getStoryArgTypes>, any>;
|
||||
|
||||
/**
|
||||
* Returns Storybook-compatible service abstractions for the `SampleDataCard` Provider.
|
||||
*/
|
||||
export const getStoryServices = (params: Params) => {
|
||||
const { simulateErrors } = params;
|
||||
const services: Services = {
|
||||
...params,
|
||||
addBasePath: (path) => {
|
||||
action('addBasePath')(path);
|
||||
return path;
|
||||
},
|
||||
getAppNavigationHandler: (path) => () => action('getAppNavigationHandler')(path),
|
||||
installSampleDataSet: async (id, defaultIndex) => {
|
||||
if (simulateErrors) {
|
||||
throw new Error('Error on install');
|
||||
}
|
||||
action('installSampleDataSet')(id, defaultIndex);
|
||||
},
|
||||
notifyError: action('notifyError'),
|
||||
notifySuccess: action('notifySuccess'),
|
||||
removeSampleDataSet: async (id) => {
|
||||
if (simulateErrors) {
|
||||
throw new Error('Error on uninstall');
|
||||
}
|
||||
action('removeSampleDataSet')(id);
|
||||
},
|
||||
};
|
||||
|
||||
return services;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Storybook arguments for `SampleDataCard`, for its stories and for
|
||||
* consuming component stories.
|
||||
*/
|
||||
export const getStoryArgTypes = () => ({
|
||||
name: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
defaultValue: mockDataSet.name,
|
||||
},
|
||||
description: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
defaultValue: mockDataSet.description,
|
||||
},
|
||||
status: {
|
||||
options: ['not_installed', 'installed', undefined],
|
||||
control: { type: 'radio' },
|
||||
defaultValue: mockDataSet.status,
|
||||
},
|
||||
includeAppLinks: {
|
||||
control: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
simulateErrors: {
|
||||
control: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the Jest-compatible service abstractions for the `NoDataCard` Provider.
|
||||
*/
|
||||
export const getMockServices = (params: Partial<Services> = {}) => {
|
||||
const services: Services = {
|
||||
addBasePath: (path) => path,
|
||||
getAppNavigationHandler: jest.fn(),
|
||||
installSampleDataSet: jest.fn(),
|
||||
notifyError: jest.fn(),
|
||||
notifySuccess: jest.fn(),
|
||||
removeSampleDataSet: jest.fn(),
|
||||
...params,
|
||||
};
|
||||
|
||||
return services;
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiCard } from '@elastic/eui';
|
||||
|
||||
import type { SampleDataSet, InstalledStatus } from '@kbn/home-sample-data-types';
|
||||
import { INSTALLED_STATUS } from './constants';
|
||||
|
||||
import { Footer } from './footer';
|
||||
|
||||
export interface Props {
|
||||
/** A Sample Data Set to display. */
|
||||
sampleDataSet: SampleDataSet;
|
||||
/** A resolved, themed image to display in the card. */
|
||||
imagePath: string;
|
||||
/** A handler to invoke when the status of a Sample Data Set is changed. */
|
||||
onStatusChange: (id: string, status: InstalledStatus) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pure implementation of the `SampleDataCard` component that itself
|
||||
* does not depend on any Kibana services. Still requires a
|
||||
* `SampleDataCardProvider` for its dependencies to render and function.
|
||||
*/
|
||||
export const SampleDataCard = ({
|
||||
sampleDataSet,
|
||||
imagePath: image,
|
||||
onStatusChange: onAction,
|
||||
}: Props) => {
|
||||
const { name: title, description, id } = sampleDataSet;
|
||||
|
||||
const betaBadgeProps = {
|
||||
label: sampleDataSet.status === INSTALLED_STATUS ? INSTALLED_STATUS : null,
|
||||
};
|
||||
|
||||
const footer = <Footer {...{ sampleDataSet, onAction }} />;
|
||||
|
||||
return (
|
||||
<EuiCard
|
||||
textAlign="left"
|
||||
paddingSize="m"
|
||||
data-test-subj={`sampleDataSetCard${id}`}
|
||||
{...{ image, title, description, betaBadgeProps, footer }}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { Params, getStoryArgTypes, getStoryServices, mockDataSet } from './mocks';
|
||||
import { SampleDataCardProvider } from './services';
|
||||
import { SampleDataCard } from './sample_data_card';
|
||||
|
||||
import mdx from '../README.mdx';
|
||||
|
||||
export default {
|
||||
title: 'Sample Data/Card',
|
||||
description:
|
||||
'A card describing a Sample Data Set, with options to install it, remove it, or see its saved objects.',
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
decorators: [(Story) => <div style={{ width: '433px', padding: '25px' }}>{Story()}</div>],
|
||||
} as ComponentMeta<typeof SampleDataCard>;
|
||||
|
||||
const argTypes = getStoryArgTypes();
|
||||
|
||||
export const Card = (params: Params) => {
|
||||
const { includeAppLinks, ...rest } = params;
|
||||
const sampleDataSet: SampleDataSet = {
|
||||
...mockDataSet,
|
||||
...rest,
|
||||
appLinks: includeAppLinks ? mockDataSet.appLinks : [],
|
||||
};
|
||||
|
||||
return (
|
||||
<SampleDataCardProvider {...getStoryServices(params)}>
|
||||
<SampleDataCard sampleDataSet={sampleDataSet} onStatusChange={action('onStatusChange')} />
|
||||
</SampleDataCardProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Card.argTypes = argTypes;
|
88
packages/home/sample_data_card/src/sample_data_card.test.tsx
Normal file
88
packages/home/sample_data_card/src/sample_data_card.test.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderWithIntl, mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { SampleDataCard } from './sample_data_card';
|
||||
import { SampleDataCardProvider } from './services';
|
||||
import { getMockServices, getMockDataSet } from './mocks';
|
||||
import { Services } from './services';
|
||||
import { INSTALLED_STATUS, UNINSTALLED_STATUS } from './constants';
|
||||
|
||||
describe('SampleDataCard', () => {
|
||||
const onStatusChange = jest.fn();
|
||||
const sampleDataSet = getMockDataSet();
|
||||
|
||||
beforeAll(() => jest.resetAllMocks());
|
||||
|
||||
const render = (element: React.ReactElement, services: Partial<Services> = {}) =>
|
||||
renderWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices(services)}>{element}</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
const mount = (element: React.ReactElement, services: Partial<Services> = {}) =>
|
||||
mountWithIntl(
|
||||
<SampleDataCardProvider {...getMockServices(services)}>{element}</SampleDataCardProvider>
|
||||
);
|
||||
|
||||
describe('not installed', () => {
|
||||
test('renders', () => {
|
||||
const component = render(<SampleDataCard {...{ sampleDataSet, onStatusChange }} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('installs', async () => {
|
||||
const component = mount(<SampleDataCard {...{ sampleDataSet, onStatusChange }} />);
|
||||
await act(async () => {
|
||||
component
|
||||
.find(`button[data-test-subj="addSampleDataSet${sampleDataSet.id}"]`)
|
||||
.simulate('click');
|
||||
});
|
||||
expect(onStatusChange).toHaveBeenCalledWith(sampleDataSet.id, INSTALLED_STATUS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('installed', () => {
|
||||
test('renders with app links', () => {
|
||||
const component = render(
|
||||
<SampleDataCard
|
||||
sampleDataSet={getMockDataSet({ status: 'installed' })}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders without app links', () => {
|
||||
const component = render(
|
||||
<SampleDataCard
|
||||
sampleDataSet={getMockDataSet({ status: 'installed', appLinks: [] })}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('removes', async () => {
|
||||
const component = mount(
|
||||
<SampleDataCard
|
||||
sampleDataSet={getMockDataSet({ status: 'installed', appLinks: [] })}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
component
|
||||
.find(`button[data-test-subj="removeSampleDataSet${sampleDataSet.id}"]`)
|
||||
.simulate('click');
|
||||
});
|
||||
expect(onStatusChange).toHaveBeenCalledWith(sampleDataSet.id, UNINSTALLED_STATUS);
|
||||
});
|
||||
});
|
||||
});
|
38
packages/home/sample_data_card/src/sample_data_card.tsx
Normal file
38
packages/home/sample_data_card/src/sample_data_card.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import type { SampleDataSet } from '@kbn/home-sample-data-types';
|
||||
import { useServices } from './services';
|
||||
import { SampleDataCard as Component, Props as ComponentProps } from './sample_data_card.component';
|
||||
|
||||
/**
|
||||
* Props for the `SampleDataCard` component.
|
||||
*/
|
||||
export interface Props extends Pick<ComponentProps, 'onStatusChange'> {
|
||||
/** A Sample Data Set to display. */
|
||||
sampleDataSet: SampleDataSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* A card representing a Sample Data Set that can be installed. Uses Kibana services to
|
||||
* display and install the data set. Requires a `SampleDataCardProvider` to render and
|
||||
* function.
|
||||
*/
|
||||
export const SampleDataCard = ({ sampleDataSet, onStatusChange }: Props) => {
|
||||
const { addBasePath } = useServices();
|
||||
const { colorMode } = useEuiTheme();
|
||||
const { darkPreviewImagePath, previewImagePath } = sampleDataSet;
|
||||
const path =
|
||||
colorMode === 'DARK' && darkPreviewImagePath ? darkPreviewImagePath : previewImagePath;
|
||||
const imagePath = useMemo(() => addBasePath(path), [addBasePath, path]);
|
||||
|
||||
return <Component {...{ sampleDataSet, imagePath, onStatusChange }} />;
|
||||
};
|
159
packages/home/sample_data_card/src/services.tsx
Normal file
159
packages/home/sample_data_card/src/services.tsx
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { FC, MouseEventHandler, useContext } from 'react';
|
||||
import { EuiGlobalToastListToast as EuiToast } from '@elastic/eui';
|
||||
|
||||
import { SAMPLE_DATA_API } from './constants';
|
||||
|
||||
type NavigateToUrl = (url: string) => Promise<void> | void;
|
||||
type UnmountCallback = () => void;
|
||||
type MountPoint<T extends HTMLElement = HTMLElement> = (element: T) => UnmountCallback;
|
||||
type ValidNotifyString = string | MountPoint<HTMLElement>;
|
||||
|
||||
type NotifyInputFields = Pick<EuiToast, Exclude<keyof EuiToast, 'id' | 'text' | 'title'>> & {
|
||||
title?: ValidNotifyString;
|
||||
text?: ValidNotifyString;
|
||||
};
|
||||
|
||||
type NotifyInput = string | NotifyInputFields;
|
||||
type NotifyFn = (notification: NotifyInput) => void;
|
||||
|
||||
/**
|
||||
* A list of services that are consumed by this component.
|
||||
*/
|
||||
export interface Services {
|
||||
addBasePath: (path: string) => string;
|
||||
getAppNavigationHandler: (path: string) => MouseEventHandler;
|
||||
installSampleDataSet: (id: string, defaultIndex: string) => Promise<void>;
|
||||
notifyError: NotifyFn;
|
||||
notifySuccess: NotifyFn;
|
||||
removeSampleDataSet: (id: string, defaultIndex: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const Context = React.createContext<Services | null>(null);
|
||||
|
||||
/**
|
||||
* A Context Provider that provides services to the component and its dependencies.
|
||||
*/
|
||||
export const SampleDataCardProvider: FC<Services> = ({ children, ...services }) => {
|
||||
const {
|
||||
addBasePath,
|
||||
getAppNavigationHandler,
|
||||
installSampleDataSet,
|
||||
notifyError,
|
||||
notifySuccess,
|
||||
removeSampleDataSet,
|
||||
} = services;
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
addBasePath,
|
||||
getAppNavigationHandler,
|
||||
installSampleDataSet,
|
||||
notifyError,
|
||||
notifySuccess,
|
||||
removeSampleDataSet,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export interface KibanaDependencies {
|
||||
coreStart: {
|
||||
application: {
|
||||
navigateToUrl: NavigateToUrl;
|
||||
};
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: (path: string) => string;
|
||||
};
|
||||
delete: (path: string) => Promise<unknown>;
|
||||
post: (path: string) => Promise<unknown>;
|
||||
};
|
||||
notifications: {
|
||||
toasts: {
|
||||
addDanger: NotifyFn;
|
||||
addSuccess: NotifyFn;
|
||||
};
|
||||
};
|
||||
uiSettings: {
|
||||
get: (key: string, defaultOverride?: any) => any;
|
||||
isDefault: (key: string) => boolean;
|
||||
set: (key: string, value: any) => Promise<boolean>;
|
||||
};
|
||||
};
|
||||
dataViews: {
|
||||
clearCache: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Kibana-specific Provider that maps dependencies to services.
|
||||
*/
|
||||
export const SampleDataCardKibanaProvider: FC<KibanaDependencies> = ({
|
||||
children,
|
||||
...dependencies
|
||||
}) => {
|
||||
const { application, http, notifications, uiSettings } = dependencies.coreStart;
|
||||
const clearDataViewsCache = dependencies.dataViews.clearCache;
|
||||
|
||||
const value: Services = {
|
||||
addBasePath: http.basePath.prepend,
|
||||
getAppNavigationHandler: (targetUrl) => (event) => {
|
||||
if (event.altKey || event.metaKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
application.navigateToUrl(http.basePath.prepend(targetUrl));
|
||||
},
|
||||
installSampleDataSet: async (id, defaultIndex) => {
|
||||
await http.post(`${SAMPLE_DATA_API}/${id}`);
|
||||
|
||||
if (uiSettings.isDefault('defaultIndex')) {
|
||||
uiSettings.set('defaultIndex', defaultIndex);
|
||||
}
|
||||
|
||||
clearDataViewsCache();
|
||||
},
|
||||
removeSampleDataSet: async (id, defaultIndex) => {
|
||||
await http.delete(`${SAMPLE_DATA_API}/${id}`);
|
||||
|
||||
if (
|
||||
!uiSettings.isDefault('defaultIndex') &&
|
||||
uiSettings.get('defaultIndex') === defaultIndex
|
||||
) {
|
||||
uiSettings.set('defaultIndex', null);
|
||||
}
|
||||
|
||||
clearDataViewsCache();
|
||||
},
|
||||
notifyError: (input) => notifications.toasts.addDanger(input),
|
||||
notifySuccess: (input) => notifications.toasts.addSuccess(input),
|
||||
};
|
||||
|
||||
return <Context.Provider {...{ value }}>{children}</Context.Provider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* React hook for accessing pre-wired services.
|
||||
*/
|
||||
export function useServices() {
|
||||
const context = useContext(Context);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'SampleDataCard Context is missing. Ensure your component or React root is wrapped with SampleDataCardContext.'
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
19
packages/home/sample_data_card/tsconfig.json
Normal file
19
packages/home/sample_data_card/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.bazel.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "target_types",
|
||||
"rootDir": "src",
|
||||
"stripInternal": false,
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react",
|
||||
"@kbn/ambient-ui-types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue