mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[home] Create Sample Data Card package (#135472)
* [home] Create Sample Data Card package * Fix issues found in CI * Update packages/home/sample_data_cards/src/sample_data_card.component.tsx Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Update packages/home/sample_data_cards/src/footer/remove_footer.tsx Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Addressing review feedback * Fix i18n, reduce dependencies * Update docs and snaps; add tests Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com>
This commit is contained in:
parent
850b8f015b
commit
e564f1f5bc
60 changed files with 2277 additions and 786 deletions
|
@ -31,6 +31,7 @@ const STORYBOOKS = [
|
|||
'expression_shape',
|
||||
'expression_tagcloud',
|
||||
'fleet',
|
||||
'home',
|
||||
'infra',
|
||||
'kibana_react',
|
||||
'lists',
|
||||
|
|
13
.github/CODEOWNERS
vendored
13
.github/CODEOWNERS
vendored
|
@ -640,16 +640,25 @@ x-pack/plugins/security_solution/public/kubernetes @elastic/awp-platform
|
|||
|
||||
# Application Experience
|
||||
|
||||
## Shared UX
|
||||
## Shared UX Team
|
||||
/src/plugins/shared_ux/ @elastic/shared-ux
|
||||
/packages/shared-ux/ @elastic/shared-ux
|
||||
/packages/shared-ux-*/ @elastic/shared-ux
|
||||
|
||||
### Kibana React (to be deprecated)
|
||||
/src/plugins/kibana_react/ @elastic/shared-ux
|
||||
/src/plugins/kibana_react/public/code_editor @elastic/shared-ux @elastic/kibana-presentation
|
||||
|
||||
### Home Plugin and Packages
|
||||
/packages/home/ @elastic/shared-ux
|
||||
/src/plugins/home/public @elastic/shared-ux
|
||||
/src/plugins/home/server/*.ts @elastic/shared-ux
|
||||
/src/plugins/home/server/services/ @elastic/shared-ux
|
||||
|
||||
### Overview Plugin and Packages
|
||||
/src/plugins/kibana_overview/ @elastic/shared-ux
|
||||
|
||||
### Code Coverage
|
||||
#CC# /src/plugins/home/public @elastic/shared-ux
|
||||
#CC# /src/plugins/home/server/services/ @elastic/shared-ux
|
||||
#CC# /src/plugins/home/ @elastic/shared-ux
|
||||
/src/plugins/kibana_overview/ @elastic/shared-ux
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"fieldFormats": "src/plugins/field_formats",
|
||||
"flot": "packages/kbn-flot-charts/lib",
|
||||
"home": "src/plugins/home",
|
||||
"homePackages": "packages/home",
|
||||
"indexPatternEditor": "src/plugins/data_view_editor",
|
||||
"indexPatternFieldEditor": "src/plugins/data_view_field_editor",
|
||||
"indexPatternManagement": "src/plugins/data_view_management",
|
||||
|
|
|
@ -200,6 +200,7 @@
|
|||
"@kbn/field-types": "link:bazel-bin/packages/kbn-field-types",
|
||||
"@kbn/flot-charts": "link:bazel-bin/packages/kbn-flot-charts",
|
||||
"@kbn/handlebars": "link:bazel-bin/packages/kbn-handlebars",
|
||||
"@kbn/home-sample-data-cards": "link:bazel-bin/packages/home/sample_data_cards",
|
||||
"@kbn/i18n": "link:bazel-bin/packages/kbn-i18n",
|
||||
"@kbn/i18n-react": "link:bazel-bin/packages/kbn-i18n-react",
|
||||
"@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter",
|
||||
|
@ -753,6 +754,7 @@
|
|||
"@types/kbn__find-used-node-modules": "link:bazel-bin/packages/kbn-find-used-node-modules/npm_module_types",
|
||||
"@types/kbn__generate": "link:bazel-bin/packages/kbn-generate/npm_module_types",
|
||||
"@types/kbn__handlebars": "link:bazel-bin/packages/kbn-handlebars/npm_module_types",
|
||||
"@types/kbn__home-sample-data-cards": "link:bazel-bin/packages/home/sample_data_cards/npm_module_types",
|
||||
"@types/kbn__i18n": "link:bazel-bin/packages/kbn-i18n/npm_module_types",
|
||||
"@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types",
|
||||
"@types/kbn__import-resolver": "link:bazel-bin/packages/kbn-import-resolver/npm_module_types",
|
||||
|
|
|
@ -63,6 +63,7 @@ filegroup(
|
|||
"//packages/core/theme/core-theme-browser:build",
|
||||
"//packages/elastic-apm-synthtrace:build",
|
||||
"//packages/elastic-safer-lodash-set:build",
|
||||
"//packages/home/sample_data_cards:build",
|
||||
"//packages/kbn-ace:build",
|
||||
"//packages/kbn-alerts:build",
|
||||
"//packages/kbn-ambient-storybook-types:build",
|
||||
|
@ -238,6 +239,7 @@ filegroup(
|
|||
"//packages/core/theme/core-theme-browser:build_types",
|
||||
"//packages/elastic-apm-synthtrace:build_types",
|
||||
"//packages/elastic-safer-lodash-set:build_types",
|
||||
"//packages/home/sample_data_cards:build_types",
|
||||
"//packages/kbn-ace:build_types",
|
||||
"//packages/kbn-alerts:build_types",
|
||||
"//packages/kbn-analytics:build_types",
|
||||
|
|
138
packages/home/sample_data_cards/BUILD.bazel
Normal file
138
packages/home/sample_data_cards/BUILD.bazel
Normal file
|
@ -0,0 +1,138 @@
|
|||
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_cards"
|
||||
PKG_REQUIRE_NAME = "@kbn/home-sample-data-cards"
|
||||
|
||||
SOURCE_FILES = glob(
|
||||
[
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.mdx",
|
||||
],
|
||||
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",
|
||||
]
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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_cards/README.mdx
Normal file
23
packages/home/sample_data_cards/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 |
|
||||
|---|---|
|
||||
| `SampleDataCards` | 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. |
|
||||
| `SampleDataCardsProvider` | Provides contextual services to `KibanaNoDataPage`. |
|
||||
| `SampleDataCardsKibanaProvider` | 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_cards/jest.config.js
Normal file
13
packages/home/sample_data_cards/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_cards'],
|
||||
};
|
8
packages/home/sample_data_cards/package.json
Normal file
8
packages/home/sample_data_cards/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@kbn/home-sample-data-cards",
|
||||
"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_cards/src/__snapshots__/sample_data_card.test.tsx.snap
generated
Normal file
275
packages/home/sample_data_cards/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_cards/src/constants.ts
Normal file
22
packages/home/sample_data_cards/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_cards/src/footer/__snapshots__/disabled_footer.test.tsx.snap
generated
Normal file
33
packages/home/sample_data_cards/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_cards/src/footer/__snapshots__/install_footer.test.tsx.snap
generated
Normal file
28
packages/home/sample_data_cards/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_cards/src/footer/__snapshots__/remove_footer.test.tsx.snap
generated
Normal file
48
packages/home/sample_data_cards/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_cards/src/footer/__snapshots__/view_button.test.tsx.snap
generated
Normal file
86
packages/home/sample_data_cards/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 { SampleDataCardsProvider } 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(
|
||||
<SampleDataCardsProvider {...getMockServices()}>{element}</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
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 '../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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { Params, getStoryArgTypes, getStoryServices, mockDataSet } from '../mocks';
|
||||
import { SampleDataCardsProvider } from '../services';
|
||||
import { Footer as Component } from '.';
|
||||
|
||||
import type { SampleDataSet } from '../types';
|
||||
|
||||
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 (
|
||||
<SampleDataCardsProvider {...getStoryServices(params)}>
|
||||
<Component sampleDataSet={sampleDataSet} onAction={action('onAction')} />
|
||||
</SampleDataCardsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
CardFooter.argTypes = argTypes;
|
40
packages/home/sample_data_cards/src/footer/index.tsx
Normal file
40
packages/home/sample_data_cards/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 { INSTALLED_STATUS, UNINSTALLED_STATUS } from '../constants';
|
||||
|
||||
import { SampleDataSet, InstalledStatus } from '../types';
|
||||
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 { SampleDataCardsProvider, 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(
|
||||
<SampleDataCardsProvider {...getMockServices()}>{element}</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
const mount = (element: React.ReactElement, params?: Partial<Services>) =>
|
||||
mountWithIntl(
|
||||
<SampleDataCardsProvider {...getMockServices({ notifyError, notifySuccess, ...params })}>
|
||||
{element}
|
||||
</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -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 { useInstall } from '../hooks';
|
||||
import type { SampleDataSet } from '../types';
|
||||
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 { SampleDataCardsProvider, 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(
|
||||
<SampleDataCardsProvider {...getMockServices()}>{element}</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
const mount = (element: React.ReactElement, params?: Partial<Services>) =>
|
||||
mountWithIntl(
|
||||
<SampleDataCardsProvider {...getMockServices({ notifyError, notifySuccess, ...params })}>
|
||||
{element}
|
||||
</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
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_cards/src/footer/remove_footer.tsx
Normal file
72
packages/home/sample_data_cards/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 { useRemove } from '../hooks';
|
||||
import type { SampleDataSet } from '../types';
|
||||
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>
|
||||
);
|
||||
};
|
|
@ -7,26 +7,27 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { renderWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { SampleDataViewDataButton } from './sample_data_view_data_button';
|
||||
import { ViewButton } from './view_button';
|
||||
import { SampleDataCardsProvider } from '../services';
|
||||
import { getMockServices } from '../mocks';
|
||||
|
||||
jest.mock('../kibana_services', () => ({
|
||||
getServices: () => ({
|
||||
addBasePath: (path) => `root${path}`,
|
||||
}),
|
||||
}));
|
||||
const render = (element: React.ReactElement) =>
|
||||
renderWithIntl(
|
||||
<SampleDataCardsProvider {...getMockServices()}>{element}</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
test('should render simple button when appLinks is empty', () => {
|
||||
const component = shallow(
|
||||
<SampleDataViewDataButton
|
||||
const component = render(
|
||||
<ViewButton
|
||||
id="ecommerce"
|
||||
name="Sample eCommerce orders"
|
||||
overviewDashboard="722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
appLinks={[]}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render popover when appLinks is not empty', () => {
|
||||
|
@ -38,15 +39,15 @@ test('should render popover when appLinks is not empty', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const component = shallow(
|
||||
<SampleDataViewDataButton
|
||||
const component = render(
|
||||
<ViewButton
|
||||
id="ecommerce"
|
||||
name="Sample eCommerce orders"
|
||||
overviewDashboard="722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
appLinks={appLinks}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render popover with ordered appLinks', () => {
|
||||
|
@ -76,13 +77,13 @@ test('should render popover with ordered appLinks', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const component = shallow(
|
||||
<SampleDataViewDataButton
|
||||
const component = render(
|
||||
<ViewButton
|
||||
id="ecommerce"
|
||||
name="Sample eCommerce orders"
|
||||
overviewDashboard="722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
appLinks={appLinks}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
122
packages/home/sample_data_cards/src/footer/view_button.tsx
Normal file
122
packages/home/sample_data_cards/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 { useServices } from '../services';
|
||||
import type { SampleDataSet } from '../types';
|
||||
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
15
packages/home/sample_data_cards/src/hooks/index.ts
Normal file
15
packages/home/sample_data_cards/src/hooks/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { useList } from './use_list';
|
||||
|
||||
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_cards/src/hooks/use_install.ts
Normal file
64
packages/home/sample_data_cards/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 { useServices } from '../services';
|
||||
import type { SampleDataSet } from '../types';
|
||||
|
||||
/**
|
||||
* 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];
|
||||
};
|
53
packages/home/sample_data_cards/src/hooks/use_list.ts
Normal file
53
packages/home/sample_data_cards/src/hooks/use_list.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { useCallback, useState, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useServices } from '../services';
|
||||
import type { SampleDataSet } from '../types';
|
||||
|
||||
/**
|
||||
* A React hook that fetches a list of Sample Data Sets from Kibana, as well as failure
|
||||
* indicators in the Kibana UI. It also provides a boolean that indicates if the list is
|
||||
* currently being fetched, as well as a method to refresh the list on demand.
|
||||
*/
|
||||
export const useList = (): [SampleDataSet[], typeof refresh, boolean] => {
|
||||
const { fetchSampleDataSets, notifyError } = useServices();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sampleDataSets, setSampleDataSets] = useState<SampleDataSet[]>([]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const sets = await fetchSampleDataSets();
|
||||
setIsLoading(false);
|
||||
|
||||
setSampleDataSets(
|
||||
sets.sort((a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
})
|
||||
);
|
||||
} catch (fetchError) {
|
||||
notifyError({
|
||||
title: i18n.translate('homePackages.sampleDataSet.unableToLoadListErrorMessage', {
|
||||
defaultMessage: 'Unable to load sample data sets list',
|
||||
}),
|
||||
text: `${fetchError.message}`,
|
||||
});
|
||||
setIsLoading(false);
|
||||
setSampleDataSets([]);
|
||||
}
|
||||
}, [fetchSampleDataSets, notifyError]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
return [sampleDataSets, refresh, isLoading];
|
||||
};
|
61
packages/home/sample_data_cards/src/hooks/use_remove.ts
Normal file
61
packages/home/sample_data_cards/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 { useServices } from '../services';
|
||||
import type { SampleDataSet } from '../types';
|
||||
|
||||
/**
|
||||
* 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];
|
||||
};
|
13
packages/home/sample_data_cards/src/index.ts
Normal file
13
packages/home/sample_data_cards/src/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 { SampleDataCards } from './sample_data_cards';
|
||||
export { SampleDataCard } from './sample_data_card';
|
||||
export type { Props as SampleDataCardProps } from './sample_data_card';
|
||||
|
||||
export { SampleDataCardsKibanaProvider, SampleDataCardsProvider } from './services';
|
BIN
packages/home/sample_data_cards/src/mocks/dashboard.png
Normal file
BIN
packages/home/sample_data_cards/src/mocks/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
BIN
packages/home/sample_data_cards/src/mocks/dashboard_dark.png
Normal file
BIN
packages/home/sample_data_cards/src/mocks/dashboard_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
6
packages/home/sample_data_cards/src/mocks/icon.svg
Normal file
6
packages/home/sample_data_cards/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 |
141
packages/home/sample_data_cards/src/mocks/index.ts
Normal file
141
packages/home/sample_data_cards/src/mocks/index.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 previewImagePath from './dashboard.png';
|
||||
import darkPreviewImagePath from './dashboard_dark.png';
|
||||
import iconPath from './icon.svg';
|
||||
|
||||
import { Services } from '../services';
|
||||
import { SampleDataSet } from '../types';
|
||||
|
||||
/**
|
||||
* 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 `SampleDataCards` Provider.
|
||||
*/
|
||||
export const getStoryServices = (params: Params) => {
|
||||
const { simulateErrors } = params;
|
||||
const services: Services = {
|
||||
...params,
|
||||
addBasePath: (path) => {
|
||||
action('addBasePath')(path);
|
||||
return path;
|
||||
},
|
||||
fetchSampleDataSets: async () => {
|
||||
return [];
|
||||
},
|
||||
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 `SampleDataCards`, 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,
|
||||
fetchSampleDataSets: jest.fn(),
|
||||
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 { INSTALLED_STATUS } from './constants';
|
||||
|
||||
import type { SampleDataSet, InstalledStatus } from './types';
|
||||
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
|
||||
* `SampleDataCardsProvider` 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,50 @@
|
|||
/*
|
||||
* 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 { Params, getStoryArgTypes, getStoryServices, mockDataSet } from './mocks';
|
||||
import { SampleDataCardsProvider } from './services';
|
||||
import { SampleDataCard } from './sample_data_card';
|
||||
|
||||
import type { SampleDataSet } from './types';
|
||||
|
||||
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 (
|
||||
<SampleDataCardsProvider {...getStoryServices(params)}>
|
||||
<SampleDataCard sampleDataSet={sampleDataSet} onStatusChange={action('onStatusChange')} />
|
||||
</SampleDataCardsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Card.argTypes = argTypes;
|
|
@ -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 { SampleDataCardsProvider } 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(
|
||||
<SampleDataCardsProvider {...getMockServices(services)}>{element}</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
const mount = (element: React.ReactElement, services: Partial<Services> = {}) =>
|
||||
mountWithIntl(
|
||||
<SampleDataCardsProvider {...getMockServices(services)}>{element}</SampleDataCardsProvider>
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
39
packages/home/sample_data_cards/src/sample_data_card.tsx
Normal file
39
packages/home/sample_data_cards/src/sample_data_card.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { useServices } from './services';
|
||||
import { SampleDataCard as Component, Props as ComponentProps } from './sample_data_card.component';
|
||||
|
||||
import type { SampleDataSet } from './types';
|
||||
|
||||
/**
|
||||
* 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 `SampleDataCardsProvider` 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 }} />;
|
||||
};
|
37
packages/home/sample_data_cards/src/sample_data_cards.tsx
Normal file
37
packages/home/sample_data_cards/src/sample_data_cards.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
|
||||
import { FlexGridColumns } from '@elastic/eui/src/components/flex/flex_grid';
|
||||
|
||||
import { useList } from './hooks';
|
||||
import { SampleDataCard } from './sample_data_card';
|
||||
|
||||
/**
|
||||
* Props for the `SampleDataCards` component.
|
||||
*/
|
||||
export interface Props {
|
||||
/** Number of columns, defaults to 3. */
|
||||
columns?: FlexGridColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and displays a collection of Sample Data Sets in a grid.
|
||||
*/
|
||||
export const SampleDataCards = ({ columns = 3 }: Props) => {
|
||||
const [sampleDataSets, refresh] = useList();
|
||||
|
||||
const cards = sampleDataSets.map((sampleDataSet) => (
|
||||
<EuiFlexItem key={sampleDataSet.id}>
|
||||
<SampleDataCard sampleDataSet={sampleDataSet} onStatusChange={refresh} />
|
||||
</EuiFlexItem>
|
||||
));
|
||||
|
||||
return <EuiFlexGrid {...{ columns }}>{cards}</EuiFlexGrid>;
|
||||
};
|
165
packages/home/sample_data_cards/src/services.tsx
Normal file
165
packages/home/sample_data_cards/src/services.tsx
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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';
|
||||
import type { SampleDataSet } from './types';
|
||||
|
||||
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;
|
||||
fetchSampleDataSets: () => Promise<SampleDataSet[]>;
|
||||
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 SampleDataCardsProvider: FC<Services> = ({ children, ...services }) => {
|
||||
const {
|
||||
addBasePath,
|
||||
fetchSampleDataSets,
|
||||
getAppNavigationHandler,
|
||||
installSampleDataSet,
|
||||
notifyError,
|
||||
notifySuccess,
|
||||
removeSampleDataSet,
|
||||
} = services;
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
addBasePath,
|
||||
fetchSampleDataSets,
|
||||
getAppNavigationHandler,
|
||||
installSampleDataSet,
|
||||
notifyError,
|
||||
notifySuccess,
|
||||
removeSampleDataSet,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
interface KibanaDependencies {
|
||||
coreStart: {
|
||||
application: {
|
||||
navigateToUrl: NavigateToUrl;
|
||||
};
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: (path: string) => string;
|
||||
};
|
||||
delete: (path: string) => Promise<unknown>;
|
||||
get: (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 SampleDataCardsKibanaProvider: FC<KibanaDependencies> = ({
|
||||
children,
|
||||
...dependencies
|
||||
}) => {
|
||||
const { application, http, notifications, uiSettings } = dependencies.coreStart;
|
||||
const clearDataViewsCache = dependencies.dataViews.clearCache;
|
||||
|
||||
const value: Services = {
|
||||
addBasePath: http.basePath.prepend,
|
||||
fetchSampleDataSets: async () => (await http.get(SAMPLE_DATA_API)) as SampleDataSet[],
|
||||
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(
|
||||
'SampleDataCards Context is missing. Ensure your component or React root is wrapped with SampleDataCardsContext.'
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
48
packages/home/sample_data_cards/src/types.ts
Normal file
48
packages/home/sample_data_cards/src/types.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { INSTALLED_STATUS, UNINSTALLED_STATUS } from './constants';
|
||||
|
||||
/**
|
||||
* A relevant subset of the `AppLinkData` type.
|
||||
*
|
||||
* @see src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types
|
||||
*/
|
||||
export interface AppLink {
|
||||
icon: string;
|
||||
label: string;
|
||||
order?: number;
|
||||
path: string;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types
|
||||
*/
|
||||
export type InstalledStatus = typeof INSTALLED_STATUS | typeof UNINSTALLED_STATUS | 'unknown';
|
||||
|
||||
/**
|
||||
* A subset of properties from a Sample Data Set that are relevant to these components.
|
||||
* Included here as the type is not in a package for consumption by a package.
|
||||
*
|
||||
* @see src/plugins/home/server/services/sample_data/lib/sample_dataset_schema
|
||||
* @see src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types
|
||||
*/
|
||||
export interface SampleDataSet {
|
||||
appLinks: AppLink[];
|
||||
defaultIndex: string;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
overviewDashboard: string;
|
||||
previewImagePath: string;
|
||||
darkPreviewImagePath?: string;
|
||||
iconPath?: string;
|
||||
status?: InstalledStatus;
|
||||
statusMsg?: string;
|
||||
}
|
19
packages/home/sample_data_cards/tsconfig.json
Normal file
19
packages/home/sample_data_cards/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/**/*"
|
||||
]
|
||||
}
|
|
@ -27,6 +27,7 @@ export const BAZEL_PACKAGE_DIRS = [
|
|||
'packages/analytics/shippers',
|
||||
'packages/analytics/shippers/elastic_v3',
|
||||
'packages/core/*',
|
||||
'packages/home',
|
||||
'x-pack/packages/ml',
|
||||
];
|
||||
|
||||
|
|
|
@ -8,12 +8,11 @@
|
|||
|
||||
// Please also add new aliases to test/scripts/jenkins_storybook.sh
|
||||
export const storybookAliases = {
|
||||
unified_search: 'src/plugins/unified_search/.storybook',
|
||||
coloring: 'packages/kbn-coloring/.storybook',
|
||||
apm: 'x-pack/plugins/apm/.storybook',
|
||||
canvas: 'x-pack/plugins/canvas/storybook',
|
||||
ci_composite: '.ci/.storybook',
|
||||
cloud: 'x-pack/plugins/cloud/.storybook',
|
||||
coloring: 'packages/kbn-coloring/.storybook',
|
||||
controls: 'src/plugins/controls/storybook',
|
||||
custom_integrations: 'src/plugins/custom_integrations/storybook',
|
||||
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook',
|
||||
|
@ -31,6 +30,7 @@ export const storybookAliases = {
|
|||
expression_shape: 'src/plugins/expression_shape/.storybook',
|
||||
expression_tagcloud: 'src/plugins/chart_expressions/expression_tagcloud/.storybook',
|
||||
fleet: 'x-pack/plugins/fleet/.storybook',
|
||||
home: 'src/plugins/home/.storybook',
|
||||
infra: 'x-pack/plugins/infra/.storybook',
|
||||
kibana_react: 'src/plugins/kibana_react/.storybook',
|
||||
lists: 'x-pack/plugins/lists/.storybook',
|
||||
|
@ -39,4 +39,5 @@ export const storybookAliases = {
|
|||
security_solution: 'x-pack/plugins/security_solution/.storybook',
|
||||
shared_ux: 'packages/kbn-shared-ux-storybook/src/config',
|
||||
ui_actions_enhanced: 'src/plugins/ui_actions_enhanced/.storybook',
|
||||
unified_search: 'src/plugins/unified_search/.storybook',
|
||||
};
|
||||
|
|
17
src/plugins/home/.storybook/main.ts
Normal file
17
src/plugins/home/.storybook/main.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { defaultConfig } from '@kbn/storybook';
|
||||
|
||||
module.exports = {
|
||||
...defaultConfig,
|
||||
stories: ['../../../../packages/home/**/*.stories.+(tsx|mdx)', '../**/*.stories.+(tsx|mdx)'],
|
||||
reactOptions: {
|
||||
strictMode: true,
|
||||
},
|
||||
};
|
21
src/plugins/home/.storybook/manager.ts
Normal file
21
src/plugins/home/.storybook/manager.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { addons } from '@storybook/addons';
|
||||
import { create } from '@storybook/theming';
|
||||
import { PANEL_ID } from '@storybook/addon-actions';
|
||||
|
||||
addons.setConfig({
|
||||
theme: create({
|
||||
base: 'light',
|
||||
brandTitle: 'Home Storybook',
|
||||
brandUrl: 'https://github.com/elastic/kibana/tree/main/src/plugins/home',
|
||||
}),
|
||||
showPanel: true.valueOf,
|
||||
selectedPanel: PANEL_ID,
|
||||
});
|
|
@ -16,6 +16,9 @@ import {
|
|||
KibanaThemeProvider,
|
||||
RedirectAppLinks,
|
||||
} from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { SampleDataCardsKibanaProvider } from '@kbn/home-sample-data-cards';
|
||||
|
||||
// @ts-ignore
|
||||
import { HomeApp } from './components/home_app';
|
||||
import { getServices } from './kibana_services';
|
||||
|
@ -29,7 +32,7 @@ export const renderApp = async (
|
|||
history: ScopedHistory
|
||||
) => {
|
||||
const homeTitle = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' });
|
||||
const { featureCatalogue, chrome } = getServices();
|
||||
const { featureCatalogue, chrome, dataViewsService: dataViews } = getServices();
|
||||
|
||||
// all the directories could be get in "start" phase of plugin after all of the legacy plugins will be moved to a NP
|
||||
const directories = featureCatalogue.get();
|
||||
|
@ -44,7 +47,9 @@ export const renderApp = async (
|
|||
<RedirectAppLinks application={coreStart.application}>
|
||||
<KibanaThemeProvider theme$={theme$}>
|
||||
<KibanaContextProvider services={{ ...coreStart }}>
|
||||
<HomeApp directories={directories} solutions={solutions} />
|
||||
<SampleDataCardsKibanaProvider {...{ coreStart, dataViews }}>
|
||||
<HomeApp directories={directories} solutions={solutions} />
|
||||
</SampleDataCardsKibanaProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaThemeProvider>
|
||||
</RedirectAppLinks>,
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should render popover when appLinks is not empty 1`] = `
|
||||
<EuiPopover
|
||||
anchorPosition="downCenter"
|
||||
button={
|
||||
<EuiButton
|
||||
aria-label="View Sample eCommerce orders"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
>
|
||||
View data
|
||||
</EuiButton>
|
||||
}
|
||||
closePopover={[Function]}
|
||||
data-test-subj="launchSampleDataSetecommerce"
|
||||
display="inlineBlock"
|
||||
hasArrow={true}
|
||||
id="sampleDataLinksecommerce"
|
||||
isOpen={false}
|
||||
ownFocus={true}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"id": 0,
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "viewSampleDataSetecommerce-dashboard",
|
||||
"href": "root/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="dashboardApp"
|
||||
/>,
|
||||
"name": "Dashboard",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"href": "rootapp/myAppPath",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="logoKibana"
|
||||
/>,
|
||||
"name": "myAppLabel",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
size="m"
|
||||
/>
|
||||
</EuiPopover>
|
||||
`;
|
||||
|
||||
exports[`should render popover with ordered appLinks 1`] = `
|
||||
<EuiPopover
|
||||
anchorPosition="downCenter"
|
||||
button={
|
||||
<EuiButton
|
||||
aria-label="View Sample eCommerce orders"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
>
|
||||
View data
|
||||
</EuiButton>
|
||||
}
|
||||
closePopover={[Function]}
|
||||
data-test-subj="launchSampleDataSetecommerce"
|
||||
display="inlineBlock"
|
||||
hasArrow={true}
|
||||
id="sampleDataLinksecommerce"
|
||||
isOpen={false}
|
||||
ownFocus={true}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={
|
||||
Array [
|
||||
Object {
|
||||
"id": 0,
|
||||
"items": Array [
|
||||
Object {
|
||||
"href": "rootapp/myAppPath",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="logoKibana"
|
||||
/>,
|
||||
"name": "myAppLabel[-1]",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "viewSampleDataSetecommerce-dashboard",
|
||||
"href": "root/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="dashboardApp"
|
||||
/>,
|
||||
"name": "Dashboard",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"href": "rootapp/myAppPath",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="logoKibana"
|
||||
/>,
|
||||
"name": "myAppLabel[3]",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"href": "rootapp/myAppPath",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="logoKibana"
|
||||
/>,
|
||||
"name": "myAppLabel[5]",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"href": "rootapp/myAppPath",
|
||||
"icon": <EuiIcon
|
||||
size="m"
|
||||
type="logoKibana"
|
||||
/>,
|
||||
"name": "myAppLabel",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
size="m"
|
||||
/>
|
||||
</EuiPopover>
|
||||
`;
|
||||
|
||||
exports[`should render simple button when appLinks is empty 1`] = `
|
||||
<EuiButton
|
||||
aria-label="View Sample eCommerce orders"
|
||||
data-test-subj="launchSampleDataSetecommerce"
|
||||
onClick={[Function]}
|
||||
>
|
||||
View data
|
||||
</EuiButton>
|
||||
`;
|
|
@ -9,5 +9,4 @@
|
|||
@import 'solutions_section';
|
||||
@import 'add_data';
|
||||
@import 'manage_data';
|
||||
@import 'sample_data_set_cards';
|
||||
@import 'tutorial/tutorial';
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.homSampleDataSetCards {
|
||||
min-height: 1px; // IE Fix
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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 PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiCard,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export const INSTALLED_STATUS = 'installed';
|
||||
export const UNINSTALLED_STATUS = 'not_installed';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { SampleDataViewDataButton } from './sample_data_view_data_button';
|
||||
|
||||
export class SampleDataSetCard extends React.Component {
|
||||
isInstalled = () => {
|
||||
if (this.props.status === INSTALLED_STATUS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
install = () => {
|
||||
this.props.onInstall(this.props.id);
|
||||
};
|
||||
|
||||
uninstall = () => {
|
||||
this.props.onUninstall(this.props.id);
|
||||
};
|
||||
|
||||
renderBtn = () => {
|
||||
switch (this.props.status) {
|
||||
case INSTALLED_STATUS:
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
isLoading={this.props.isProcessing}
|
||||
onClick={this.uninstall}
|
||||
color="danger"
|
||||
data-test-subj={`removeSampleDataSet${this.props.id}`}
|
||||
flush="left"
|
||||
aria-label={
|
||||
this.props.isProcessing
|
||||
? i18n.translate('home.sampleDataSetCard.removingButtonAriaLabel', {
|
||||
defaultMessage: 'Removing {datasetName}',
|
||||
values: {
|
||||
datasetName: this.props.name,
|
||||
},
|
||||
})
|
||||
: i18n.translate('home.sampleDataSetCard.removeButtonAriaLabel', {
|
||||
defaultMessage: 'Remove {datasetName}',
|
||||
values: {
|
||||
datasetName: this.props.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{this.props.isProcessing ? (
|
||||
<FormattedMessage
|
||||
id="home.sampleDataSetCard.removingButtonLabel"
|
||||
defaultMessage="Removing"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="home.sampleDataSetCard.removeButtonLabel"
|
||||
defaultMessage="Remove"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SampleDataViewDataButton
|
||||
id={this.props.id}
|
||||
name={this.props.name}
|
||||
overviewDashboard={this.props.overviewDashboard}
|
||||
appLinks={this.props.appLinks}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
case UNINSTALLED_STATUS:
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={this.props.isProcessing}
|
||||
onClick={this.install}
|
||||
data-test-subj={`addSampleDataSet${this.props.id}`}
|
||||
aria-label={
|
||||
this.props.isProcessing
|
||||
? i18n.translate('home.sampleDataSetCard.addingButtonAriaLabel', {
|
||||
defaultMessage: 'Adding {datasetName}',
|
||||
values: {
|
||||
datasetName: this.props.name,
|
||||
},
|
||||
})
|
||||
: i18n.translate('home.sampleDataSetCard.addButtonAriaLabel', {
|
||||
defaultMessage: 'Add {datasetName}',
|
||||
values: {
|
||||
datasetName: this.props.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{this.props.isProcessing ? (
|
||||
<FormattedMessage
|
||||
id="home.sampleDataSetCard.addingButtonLabel"
|
||||
defaultMessage="Adding"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="home.sampleDataSetCard.addButtonLabel"
|
||||
defaultMessage="Add data"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
default: {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="home.sampleDataSetCard.default.unableToVerifyErrorMessage"
|
||||
defaultMessage="Unable to verify dataset status, error: {statusMsg}"
|
||||
values={{ statusMsg: this.props.statusMsg }}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<EuiButton
|
||||
isDisabled
|
||||
data-test-subj={`addSampleDataSet${this.props.id}`}
|
||||
aria-label={i18n.translate('home.sampleDataSetCard.default.addButtonAriaLabel', {
|
||||
defaultMessage: 'Add {datasetName}',
|
||||
values: {
|
||||
datasetName: this.props.name,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="home.sampleDataSetCard.default.addButtonLabel"
|
||||
defaultMessage="Add data"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EuiCard
|
||||
textAlign="left"
|
||||
className="homSampleDataSetCard"
|
||||
image={this.props.previewUrl}
|
||||
title={this.props.name}
|
||||
description={this.props.description}
|
||||
betaBadgeProps={{ label: this.isInstalled() ? 'INSTALLED' : null }}
|
||||
footer={this.renderBtn()}
|
||||
data-test-subj={`sampleDataSetCard${this.props.id}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SampleDataSetCard.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
overviewDashboard: PropTypes.string.isRequired,
|
||||
appLinks: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
path: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
order: PropTypes.number,
|
||||
})
|
||||
).isRequired,
|
||||
status: PropTypes.oneOf([INSTALLED_STATUS, UNINSTALLED_STATUS, 'unknown']).isRequired,
|
||||
isProcessing: PropTypes.bool.isRequired,
|
||||
statusMsg: PropTypes.string,
|
||||
previewUrl: PropTypes.string.isRequired,
|
||||
onInstall: PropTypes.func.isRequired,
|
||||
onUninstall: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,205 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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 _ from 'lodash';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { SampleDataSetCard, INSTALLED_STATUS, UNINSTALLED_STATUS } from './sample_data_set_card';
|
||||
|
||||
import { getServices } from '../kibana_services';
|
||||
|
||||
import {
|
||||
listSampleDataSets,
|
||||
installSampleDataSet,
|
||||
uninstallSampleDataSet,
|
||||
} from '../sample_data_client';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class SampleDataSetCards extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.toastNotifications = getServices().toastNotifications;
|
||||
|
||||
this.state = {
|
||||
sampleDataSets: [],
|
||||
processingStatus: {},
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this._isMounted = true;
|
||||
|
||||
this.loadSampleDataSets();
|
||||
}
|
||||
|
||||
loadSampleDataSets = async () => {
|
||||
let sampleDataSets;
|
||||
try {
|
||||
sampleDataSets = await listSampleDataSets();
|
||||
} catch (fetchError) {
|
||||
this.toastNotifications.addDanger({
|
||||
title: i18n.translate('home.sampleDataSet.unableToLoadListErrorMessage', {
|
||||
defaultMessage: 'Unable to load sample data sets list',
|
||||
}),
|
||||
text: `${fetchError.message}`,
|
||||
});
|
||||
sampleDataSets = [];
|
||||
}
|
||||
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
sampleDataSets: sampleDataSets.sort((a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
install = async (id) => {
|
||||
const targetSampleDataSet = this.state.sampleDataSets.find((sampleDataSet) => {
|
||||
return sampleDataSet.id === id;
|
||||
});
|
||||
|
||||
this.setState((prevState) => ({
|
||||
processingStatus: { ...prevState.processingStatus, [id]: true },
|
||||
}));
|
||||
|
||||
try {
|
||||
await installSampleDataSet(id, targetSampleDataSet.defaultIndex);
|
||||
await this.loadSampleDataSets(); // reload the list of sample data sets
|
||||
} catch (fetchError) {
|
||||
if (this._isMounted) {
|
||||
this.setState((prevState) => ({
|
||||
processingStatus: { ...prevState.processingStatus, [id]: false },
|
||||
}));
|
||||
}
|
||||
this.toastNotifications.addDanger({
|
||||
title: i18n.translate('home.sampleDataSet.unableToInstallErrorMessage', {
|
||||
defaultMessage: 'Unable to install sample data set: {name}',
|
||||
values: { name: targetSampleDataSet.name },
|
||||
}),
|
||||
text: `${fetchError.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isMounted) {
|
||||
this.setState((prevState) => ({
|
||||
processingStatus: { ...prevState.processingStatus, [id]: false },
|
||||
sampleDataSets: prevState.sampleDataSets.map((sampleDataSet) => {
|
||||
if (sampleDataSet.id === id) {
|
||||
sampleDataSet.status = INSTALLED_STATUS;
|
||||
}
|
||||
return sampleDataSet;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
this.toastNotifications.addSuccess({
|
||||
title: i18n.translate('home.sampleDataSet.installedLabel', {
|
||||
defaultMessage: '{name} installed',
|
||||
values: { name: targetSampleDataSet.name },
|
||||
}),
|
||||
['data-test-subj']: 'sampleDataSetInstallToast',
|
||||
});
|
||||
};
|
||||
|
||||
uninstall = async (id) => {
|
||||
const targetSampleDataSet = this.state.sampleDataSets.find((sampleDataSet) => {
|
||||
return sampleDataSet.id === id;
|
||||
});
|
||||
|
||||
this.setState((prevState) => ({
|
||||
processingStatus: { ...prevState.processingStatus, [id]: true },
|
||||
}));
|
||||
|
||||
try {
|
||||
await uninstallSampleDataSet(id, targetSampleDataSet.defaultIndex);
|
||||
} catch (fetchError) {
|
||||
if (this._isMounted) {
|
||||
this.setState((prevState) => ({
|
||||
processingStatus: { ...prevState.processingStatus, [id]: false },
|
||||
}));
|
||||
}
|
||||
this.toastNotifications.addDanger({
|
||||
title: i18n.translate('home.sampleDataSet.unableToUninstallErrorMessage', {
|
||||
defaultMessage: 'Unable to uninstall sample data set: {name}',
|
||||
values: { name: targetSampleDataSet.name },
|
||||
}),
|
||||
text: `${fetchError.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isMounted) {
|
||||
this.setState((prevState) => ({
|
||||
processingStatus: { ...prevState.processingStatus, [id]: false },
|
||||
sampleDataSets: prevState.sampleDataSets.map((sampleDataSet) => {
|
||||
if (sampleDataSet.id === id) {
|
||||
sampleDataSet.status = UNINSTALLED_STATUS;
|
||||
}
|
||||
return sampleDataSet;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
this.toastNotifications.addSuccess({
|
||||
title: i18n.translate('home.sampleDataSet.uninstalledLabel', {
|
||||
defaultMessage: '{name} uninstalled',
|
||||
values: { name: targetSampleDataSet.name },
|
||||
}),
|
||||
['data-test-subj']: 'sampleDataSetUninstallToast',
|
||||
});
|
||||
};
|
||||
|
||||
lightOrDarkImage = (sampleDataSet) => {
|
||||
return getServices().uiSettings.get('theme:darkMode') && sampleDataSet.darkPreviewImagePath
|
||||
? sampleDataSet.darkPreviewImagePath
|
||||
: sampleDataSet.previewImagePath;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EuiFlexGrid columns={3} className="homSampleDataSetCards">
|
||||
{this.state.sampleDataSets.map((sampleDataSet) => {
|
||||
return (
|
||||
<EuiFlexItem key={sampleDataSet.id}>
|
||||
<SampleDataSetCard
|
||||
id={sampleDataSet.id}
|
||||
description={sampleDataSet.description}
|
||||
name={sampleDataSet.name}
|
||||
overviewDashboard={sampleDataSet.overviewDashboard}
|
||||
appLinks={sampleDataSet.appLinks}
|
||||
status={sampleDataSet.status}
|
||||
isProcessing={_.get(this.state.processingStatus, sampleDataSet.id, false)}
|
||||
statusMsg={sampleDataSet.statusMsg}
|
||||
previewUrl={this.props.addBasePath(this.lightOrDarkImage(sampleDataSet))}
|
||||
onInstall={this.install}
|
||||
onUninstall={this.uninstall}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGrid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SampleDataSetCards.propTypes = {
|
||||
addBasePath: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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 PropTypes from 'prop-types';
|
||||
import { sortBy } from 'lodash';
|
||||
import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getServices } from '../kibana_services';
|
||||
import { createAppNavigationHandler } from './app_navigation_handler';
|
||||
|
||||
export class SampleDataViewDataButton extends React.Component {
|
||||
addBasePath = getServices().addBasePath;
|
||||
|
||||
state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
|
||||
togglePopoverVisibility = () => {
|
||||
this.setState((prevState) => ({
|
||||
isPopoverOpen: !prevState.isPopoverOpen,
|
||||
}));
|
||||
};
|
||||
|
||||
closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const viewDataButtonLabel = i18n.translate('home.sampleDataSetCard.viewDataButtonLabel', {
|
||||
defaultMessage: 'View data',
|
||||
});
|
||||
const viewDataButtonAriaLabel = i18n.translate(
|
||||
'home.sampleDataSetCard.viewDataButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View {datasetName}',
|
||||
values: {
|
||||
datasetName: this.props.name,
|
||||
},
|
||||
}
|
||||
);
|
||||
const dashboardPath = `/app/dashboards#/view/${this.props.overviewDashboard}`;
|
||||
|
||||
if (this.props.appLinks.length === 0) {
|
||||
return (
|
||||
<EuiButton
|
||||
onClick={createAppNavigationHandler(dashboardPath)}
|
||||
data-test-subj={`launchSampleDataSet${this.props.id}`}
|
||||
aria-label={viewDataButtonAriaLabel}
|
||||
>
|
||||
{viewDataButtonLabel}
|
||||
</EuiButton>
|
||||
);
|
||||
}
|
||||
|
||||
const dashboardAppLink = {
|
||||
path: dashboardPath,
|
||||
label: i18n.translate('home.sampleDataSetCard.dashboardLinkLabel', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
icon: 'dashboardApp',
|
||||
order: 0,
|
||||
'data-test-subj': `viewSampleDataSet${this.props.id}-dashboard`,
|
||||
};
|
||||
|
||||
const sortedItems = sortBy([dashboardAppLink, ...this.props.appLinks], 'order');
|
||||
const items = sortedItems.map(({ path, label, icon, ...rest }) => {
|
||||
return {
|
||||
name: label,
|
||||
icon: <EuiIcon type={icon} size="m" />,
|
||||
href: this.addBasePath(path),
|
||||
onClick: createAppNavigationHandler(path),
|
||||
...(rest['data-test-subj'] ? { 'data-test-subj': rest['data-test-subj'] } : {}),
|
||||
};
|
||||
});
|
||||
|
||||
/** @typedef {import('@elastic/eui').EuiContextMenuProps['panels']} EuiContextMenuPanels */
|
||||
/** @type {EuiContextMenuPanels} */
|
||||
const panels = [
|
||||
{
|
||||
id: 0,
|
||||
items,
|
||||
},
|
||||
];
|
||||
const popoverButton = (
|
||||
<EuiButton
|
||||
aria-label={viewDataButtonAriaLabel}
|
||||
onClick={this.togglePopoverVisibility}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
>
|
||||
{viewDataButtonLabel}
|
||||
</EuiButton>
|
||||
);
|
||||
return (
|
||||
<EuiPopover
|
||||
id={`sampleDataLinks${this.props.id}`}
|
||||
button={popoverButton}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downCenter"
|
||||
data-test-subj={`launchSampleDataSet${this.props.id}`}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SampleDataViewDataButton.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
overviewDashboard: PropTypes.string.isRequired,
|
||||
appLinks: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
path: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
order: PropTypes.number,
|
||||
})
|
||||
).isRequired,
|
||||
};
|
|
@ -11,9 +11,10 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { EuiFlexItem, EuiFlexGrid, EuiFlexGroup, EuiLink } from '@elastic/eui';
|
||||
import { injectI18n, FormattedMessage } from '@kbn/i18n-react';
|
||||
import { SampleDataCards } from '@kbn/home-sample-data-cards';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Synopsis } from './synopsis';
|
||||
import { SampleDataSetCards } from './sample_data_set_cards';
|
||||
import { getServices } from '../kibana_services';
|
||||
import { KibanaPageTemplate } from '@kbn/kibana-react-plugin/public';
|
||||
import { getTutorials } from '../load_tutorials';
|
||||
|
@ -37,7 +38,7 @@ class TutorialDirectoryUi extends React.Component {
|
|||
id: 'home.tutorial.tabs.sampleDataTitle',
|
||||
defaultMessage: 'Sample data',
|
||||
}),
|
||||
content: <SampleDataSetCards addBasePath={this.props.addBasePath} />,
|
||||
content: <SampleDataCards />,
|
||||
},
|
||||
...extraTabs.map(({ id, name, component: Component }) => ({
|
||||
id,
|
||||
|
|
|
@ -83,7 +83,7 @@ export class HomePublicPlugin
|
|||
trackUiMetric,
|
||||
kibanaVersion: this.initializerContext.env.packageInfo.version,
|
||||
http: coreStart.http,
|
||||
toastNotifications: core.notifications.toasts,
|
||||
toastNotifications: coreStart.notifications.toasts,
|
||||
banners: coreStart.overlays.banners,
|
||||
docLinks: coreStart.docLinks,
|
||||
savedObjectsClient: coreStart.savedObjects.client,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"declarationMap": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["common/**/*", "public/**/*", "server/**/*", "config.ts"],
|
||||
"include": ["common/**/*", "public/**/*", "server/**/*", "config.ts", ".storybook/**/*"],
|
||||
"references": [
|
||||
{ "path": "../../core/tsconfig.json" },
|
||||
{ "path": "../data_views/tsconfig.json" },
|
||||
|
|
|
@ -3900,25 +3900,25 @@
|
|||
"home.sampleData.logsSpec.webTrafficTitle": "[Logs] Trafic Internet",
|
||||
"home.sampleData.logsSpecDescription": "Exemple de données, de visualisations et de tableaux de bord pour le monitoring des logs Internet.",
|
||||
"home.sampleData.logsSpecTitle": "Exemple de logs Internet",
|
||||
"home.sampleDataSet.installedLabel": "{name} installé",
|
||||
"home.sampleDataSet.unableToInstallErrorMessage": "Impossible d'installer l'exemple d’ensemble de données : {name}.",
|
||||
"home.sampleDataSet.unableToLoadListErrorMessage": "Impossible de charger la liste des exemples d’ensemble de données",
|
||||
"home.sampleDataSet.unableToUninstallErrorMessage": "Impossible de désinstaller l'exemple d’ensemble de données : {name}.",
|
||||
"home.sampleDataSet.uninstalledLabel": "{name} désinstallé",
|
||||
"home.sampleDataSetCard.addButtonAriaLabel": "Ajouter {datasetName}",
|
||||
"home.sampleDataSetCard.addButtonLabel": "Ajouter des données",
|
||||
"home.sampleDataSetCard.addingButtonAriaLabel": "Ajout de {datasetName}",
|
||||
"home.sampleDataSetCard.addingButtonLabel": "Ajout",
|
||||
"home.sampleDataSetCard.dashboardLinkLabel": "Tableau de bord",
|
||||
"home.sampleDataSetCard.default.addButtonAriaLabel": "Ajouter {datasetName}",
|
||||
"home.sampleDataSetCard.default.addButtonLabel": "Ajouter des données",
|
||||
"home.sampleDataSetCard.default.unableToVerifyErrorMessage": "Impossible de vérifier le statut de l'ensemble de données. Erreur : {statusMsg}.",
|
||||
"home.sampleDataSetCard.removeButtonAriaLabel": "Supprimer {datasetName}",
|
||||
"home.sampleDataSetCard.removeButtonLabel": "Supprimer",
|
||||
"home.sampleDataSetCard.removingButtonAriaLabel": "Suppression de {datasetName}",
|
||||
"home.sampleDataSetCard.removingButtonLabel": "Suppression",
|
||||
"home.sampleDataSetCard.viewDataButtonAriaLabel": "Consulter {datasetName}",
|
||||
"home.sampleDataSetCard.viewDataButtonLabel": "Consulter les données",
|
||||
"homePackages.sampleDataSet.installedLabel": "{name} installé",
|
||||
"homePackages.sampleDataSet.unableToInstallErrorMessage": "Impossible d'installer l'exemple d’ensemble de données : {name}.",
|
||||
"homePackages.sampleDataSet.unableToLoadListErrorMessage": "Impossible de charger la liste des exemples d’ensemble de données",
|
||||
"homePackages.sampleDataSet.unableToUninstallErrorMessage": "Impossible de désinstaller l'exemple d’ensemble de données : {name}.",
|
||||
"homePackages.sampleDataSet.uninstalledLabel": "{name} désinstallé",
|
||||
"homePackages.sampleDataCard.addButtonAriaLabel": "Ajouter {datasetName}",
|
||||
"homePackages.sampleDataCard.addButtonLabel": "Ajouter des données",
|
||||
"homePackages.sampleDataCard.addingButtonAriaLabel": "Ajout de {datasetName}",
|
||||
"homePackages.sampleDataCard.addingButtonLabel": "Ajout",
|
||||
"homePackages.sampleDataCard.dashboardLinkLabel": "Tableau de bord",
|
||||
"homePackages.sampleDataCard.default.addButtonAriaLabel": "Ajouter {datasetName}",
|
||||
"homePackages.sampleDataCard.default.addButtonLabel": "Ajouter des données",
|
||||
"homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "Impossible de vérifier le statut de l'ensemble de données. Erreur : {statusMsg}.",
|
||||
"homePackages.sampleDataCard.removeButtonAriaLabel": "Supprimer {datasetName}",
|
||||
"homePackages.sampleDataCard.removeButtonLabel": "Supprimer",
|
||||
"homePackages.sampleDataCard.removingButtonAriaLabel": "Suppression de {datasetName}",
|
||||
"homePackages.sampleDataCard.removingButtonLabel": "Suppression",
|
||||
"homePackages.sampleDataCard.viewDataButtonAriaLabel": "Consulter {datasetName}",
|
||||
"homePackages.sampleDataCard.viewDataButtonLabel": "Consulter les données",
|
||||
"home.solutionsSection.sectionTitle": "Choisir votre solution",
|
||||
"home.tryButtonLabel": "Ajouter des intégrations",
|
||||
"home.tutorial.addDataToKibanaDescription": "En plus d'ajouter {integrationsLink}, vous pouvez essayer l'exemple de données ou charger vos propres données.",
|
||||
|
|
|
@ -3898,25 +3898,25 @@
|
|||
"home.sampleData.logsSpec.webTrafficTitle": "[ログ] Web トラフィック",
|
||||
"home.sampleData.logsSpecDescription": "Web ログを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。",
|
||||
"home.sampleData.logsSpecTitle": "サンプル Web ログ",
|
||||
"home.sampleDataSet.installedLabel": "{name} がインストールされました",
|
||||
"home.sampleDataSet.unableToInstallErrorMessage": "サンプルデータセット「{name}」をインストールできません",
|
||||
"home.sampleDataSet.unableToLoadListErrorMessage": "サンプルデータセットのリストを読み込めません",
|
||||
"home.sampleDataSet.unableToUninstallErrorMessage": "サンプルデータセット「{name}」をアンインストールできません",
|
||||
"home.sampleDataSet.uninstalledLabel": "{name} がアンインストールされました",
|
||||
"home.sampleDataSetCard.addButtonAriaLabel": "{datasetName} を追加",
|
||||
"home.sampleDataSetCard.addButtonLabel": "データの追加",
|
||||
"home.sampleDataSetCard.addingButtonAriaLabel": "{datasetName} を追加",
|
||||
"home.sampleDataSetCard.addingButtonLabel": "追加中",
|
||||
"home.sampleDataSetCard.dashboardLinkLabel": "ダッシュボード",
|
||||
"home.sampleDataSetCard.default.addButtonAriaLabel": "{datasetName} を追加",
|
||||
"home.sampleDataSetCard.default.addButtonLabel": "データの追加",
|
||||
"home.sampleDataSetCard.default.unableToVerifyErrorMessage": "データセットステータスを確認できません、エラー:{statusMsg}",
|
||||
"home.sampleDataSetCard.removeButtonAriaLabel": "{datasetName} を削除",
|
||||
"home.sampleDataSetCard.removeButtonLabel": "削除",
|
||||
"home.sampleDataSetCard.removingButtonAriaLabel": "{datasetName} を削除中",
|
||||
"home.sampleDataSetCard.removingButtonLabel": "削除中",
|
||||
"home.sampleDataSetCard.viewDataButtonAriaLabel": "{datasetName} を表示",
|
||||
"home.sampleDataSetCard.viewDataButtonLabel": "データを表示",
|
||||
"homePackages.sampleDataSet.installedLabel": "{name} がインストールされました",
|
||||
"homePackages.sampleDataSet.unableToInstallErrorMessage": "サンプルデータセット「{name}」をインストールできません",
|
||||
"homePackages.sampleDataSet.unableToLoadListErrorMessage": "サンプルデータセットのリストを読み込めません",
|
||||
"homePackages.sampleDataSet.unableToUninstallErrorMessage": "サンプルデータセット「{name}」をアンインストールできません",
|
||||
"homePackages.sampleDataSet.uninstalledLabel": "{name} がアンインストールされました",
|
||||
"homePackages.sampleDataCard.addButtonAriaLabel": "{datasetName} を追加",
|
||||
"homePackages.sampleDataCard.addButtonLabel": "データの追加",
|
||||
"homePackages.sampleDataCard.addingButtonAriaLabel": "{datasetName} を追加",
|
||||
"homePackages.sampleDataCard.addingButtonLabel": "追加中",
|
||||
"homePackages.sampleDataCard.dashboardLinkLabel": "ダッシュボード",
|
||||
"homePackages.sampleDataCard.default.addButtonAriaLabel": "{datasetName} を追加",
|
||||
"homePackages.sampleDataCard.default.addButtonLabel": "データの追加",
|
||||
"homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "データセットステータスを確認できません、エラー:{statusMsg}",
|
||||
"homePackages.sampleDataCard.removeButtonAriaLabel": "{datasetName} を削除",
|
||||
"homePackages.sampleDataCard.removeButtonLabel": "削除",
|
||||
"homePackages.sampleDataCard.removingButtonAriaLabel": "{datasetName} を削除中",
|
||||
"homePackages.sampleDataCard.removingButtonLabel": "削除中",
|
||||
"homePackages.sampleDataCard.viewDataButtonAriaLabel": "{datasetName} を表示",
|
||||
"homePackages.sampleDataCard.viewDataButtonLabel": "データを表示",
|
||||
"home.solutionsSection.sectionTitle": "ソリューションを選択",
|
||||
"home.tryButtonLabel": "統合の追加",
|
||||
"home.tutorial.addDataToKibanaDescription": "{integrationsLink}を追加するほかに、サンプルデータを試したり、独自のデータをアップロードしたりできます。",
|
||||
|
|
|
@ -3902,25 +3902,25 @@
|
|||
"home.sampleData.logsSpec.webTrafficTitle": "[日志] 网络流量",
|
||||
"home.sampleData.logsSpecDescription": "用于监测 Web 日志的样例数据、可视化和仪表板。",
|
||||
"home.sampleData.logsSpecTitle": "样例 Web 日志",
|
||||
"home.sampleDataSet.installedLabel": "{name} 已安装",
|
||||
"home.sampleDataSet.unableToInstallErrorMessage": "无法安装样例数据集:{name}",
|
||||
"home.sampleDataSet.unableToLoadListErrorMessage": "无法加载样例数据集列表",
|
||||
"home.sampleDataSet.unableToUninstallErrorMessage": "无法卸载样例数据集:{name}",
|
||||
"home.sampleDataSet.uninstalledLabel": "{name} 已卸载",
|
||||
"home.sampleDataSetCard.addButtonAriaLabel": "添加 {datasetName}",
|
||||
"home.sampleDataSetCard.addButtonLabel": "添加数据",
|
||||
"home.sampleDataSetCard.addingButtonAriaLabel": "正在添加 {datasetName}",
|
||||
"home.sampleDataSetCard.addingButtonLabel": "正在添加",
|
||||
"home.sampleDataSetCard.dashboardLinkLabel": "仪表板",
|
||||
"home.sampleDataSetCard.default.addButtonAriaLabel": "添加 {datasetName}",
|
||||
"home.sampleDataSetCard.default.addButtonLabel": "添加数据",
|
||||
"home.sampleDataSetCard.default.unableToVerifyErrorMessage": "无法确认数据集状态,错误:{statusMsg}",
|
||||
"home.sampleDataSetCard.removeButtonAriaLabel": "移除 {datasetName}",
|
||||
"home.sampleDataSetCard.removeButtonLabel": "移除",
|
||||
"home.sampleDataSetCard.removingButtonAriaLabel": "正在移除 {datasetName}",
|
||||
"home.sampleDataSetCard.removingButtonLabel": "正在移除",
|
||||
"home.sampleDataSetCard.viewDataButtonAriaLabel": "查看 {datasetName}",
|
||||
"home.sampleDataSetCard.viewDataButtonLabel": "查看数据",
|
||||
"homePackages.sampleDataSet.installedLabel": "{name} 已安装",
|
||||
"homePackages.sampleDataSet.unableToInstallErrorMessage": "无法安装样例数据集:{name}",
|
||||
"homePackages.sampleDataSet.unableToLoadListErrorMessage": "无法加载样例数据集列表",
|
||||
"homePackages.sampleDataSet.unableToUninstallErrorMessage": "无法卸载样例数据集:{name}",
|
||||
"homePackages.sampleDataSet.uninstalledLabel": "{name} 已卸载",
|
||||
"homePackages.sampleDataCard.addButtonAriaLabel": "添加 {datasetName}",
|
||||
"homePackages.sampleDataCard.addButtonLabel": "添加数据",
|
||||
"homePackages.sampleDataCard.addingButtonAriaLabel": "正在添加 {datasetName}",
|
||||
"homePackages.sampleDataCard.addingButtonLabel": "正在添加",
|
||||
"homePackages.sampleDataCard.dashboardLinkLabel": "仪表板",
|
||||
"homePackages.sampleDataCard.default.addButtonAriaLabel": "添加 {datasetName}",
|
||||
"homePackages.sampleDataCard.default.addButtonLabel": "添加数据",
|
||||
"homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "无法确认数据集状态,错误:{statusMsg}",
|
||||
"homePackages.sampleDataCard.removeButtonAriaLabel": "移除 {datasetName}",
|
||||
"homePackages.sampleDataCard.removeButtonLabel": "移除",
|
||||
"homePackages.sampleDataCard.removingButtonAriaLabel": "正在移除 {datasetName}",
|
||||
"homePackages.sampleDataCard.removingButtonLabel": "正在移除",
|
||||
"homePackages.sampleDataCard.viewDataButtonAriaLabel": "查看 {datasetName}",
|
||||
"homePackages.sampleDataCard.viewDataButtonLabel": "查看数据",
|
||||
"home.solutionsSection.sectionTitle": "选取您的解决方案",
|
||||
"home.tryButtonLabel": "添加集成",
|
||||
"home.tutorial.addDataToKibanaDescription": "除了添加 {integrationsLink} 以外,您还可以试用样例数据或上传自己的数据。",
|
||||
|
|
|
@ -3291,6 +3291,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/home-sample-data-cards@link:bazel-bin/packages/home/sample_data_cards":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/i18n-react@link:bazel-bin/packages/kbn-i18n-react":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -6791,6 +6795,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@types/kbn__home-sample-data-cards@link:bazel-bin/packages/home/sample_data_cards/npm_module_types":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@types/kbn__i18n-react@link:bazel-bin/packages/kbn-i18n-react/npm_module_types":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue