mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* kbn history * put Add Data section in panel and move to seperate component * RecentlyAccessed component * complete circle with recently accessed dashboards * record visualizations in recent accessed history * render recently accessed * do not show recently accessed panel when no recently accessed history * add test cases for home feature panels * render dropdown when more than 5 items * only add saved search when id is provided * remove border around add data cards, move set up index patterns to under cards * add dot icon to seperate recently accessed items * fix white space issues * add timelion sheet to recently accessed * fix spelling errors, better name space styles, enhance dropdown label * avoid cutting off bottom of letters, do not display separators with small screen * wrap separator (EuiIcon) in EuiText component so it is even link text * track history by object id to avoid duplicate entries when saved object is renamed * align link dropdown on right side * shift popover placement for small screens * update recently_accessed tests to look for nodes insted of using snapshots * move id to variable * change 'Recently accessed' to 'Recently viewed' * change more dropdown label * add max-width to flex item * include /app in link path, use arrow functions to remove bind in react props * add to recently accessed when saved object is saved * address cjcenizal's comments on test assertion order and react imports
This commit is contained in:
parent
ed17359c41
commit
5eea130137
25 changed files with 1416 additions and 187 deletions
|
@ -234,7 +234,7 @@
|
|||
"classnames": "2.2.5",
|
||||
"enzyme": "3.2.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-to-json": "3.1.4",
|
||||
"enzyme-to-json": "3.3.0",
|
||||
"eslint": "4.14.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-babel": "4.1.2",
|
||||
|
|
|
@ -13,6 +13,7 @@ import { DashboardConstants, createDashboardEditUrl } from './dashboard_constant
|
|||
import { SavedObjectNotFound } from 'ui/errors';
|
||||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
|
||||
uiRoutes
|
||||
.defaults(/dashboard/, {
|
||||
|
@ -64,7 +65,12 @@ uiRoutes
|
|||
resolve: {
|
||||
dash: function (savedDashboards, Notifier, $route, $location, courier, kbnUrl, AppState) {
|
||||
const id = $route.current.params.id;
|
||||
|
||||
return savedDashboards.get(id)
|
||||
.then((savedDashboard) => {
|
||||
recentlyAccessed.add(savedDashboard.getFullPath(), savedDashboard.title, id);
|
||||
return savedDashboard;
|
||||
})
|
||||
.catch((error) => {
|
||||
// Preserve BWC of v5.3.0 links for new, unsaved dashboards.
|
||||
// See https://github.com/elastic/kibana/issues/10951 for more context.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants';
|
||||
const module = uiModules.get('app/dashboard');
|
||||
|
||||
// Used only by the savedDashboards service, usually no reason to change this
|
||||
|
@ -41,6 +42,9 @@ module.factory('SavedDashboard', function (courier, config) {
|
|||
// object, clear it. It was a mistake
|
||||
clearSavedIndexPattern: true
|
||||
});
|
||||
|
||||
|
||||
this.showInRecenltyAccessed = true;
|
||||
}
|
||||
|
||||
// save these objects with the 'dashboard' type
|
||||
|
@ -77,5 +81,9 @@ module.factory('SavedDashboard', function (courier, config) {
|
|||
|
||||
SavedDashboard.searchsource = true;
|
||||
|
||||
SavedDashboard.prototype.getFullPath = function () {
|
||||
return `/app/kibana#${createDashboardEditUrl(this.id)}`;
|
||||
};
|
||||
|
||||
return SavedDashboard;
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import { StateProvider } from 'ui/state_management/state';
|
|||
import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
|
||||
const app = uiModules.get('apps/discover', [
|
||||
'kibana/notify',
|
||||
|
@ -80,7 +81,17 @@ uiRoutes
|
|||
});
|
||||
},
|
||||
savedSearch: function (courier, savedSearches, $route) {
|
||||
return savedSearches.get($route.current.params.id)
|
||||
const savedSearchId = $route.current.params.id;
|
||||
return savedSearches.get(savedSearchId)
|
||||
.then((savedSearch) => {
|
||||
if (savedSearchId) {
|
||||
recentlyAccessed.add(
|
||||
savedSearch.getFullPath(),
|
||||
savedSearch.title,
|
||||
savedSearchId);
|
||||
}
|
||||
return savedSearch;
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'search': '/discover',
|
||||
'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id
|
||||
|
|
|
@ -26,6 +26,8 @@ module.factory('SavedSearch', function (courier) {
|
|||
version: 1
|
||||
}
|
||||
});
|
||||
|
||||
this.showInRecenltyAccessed = true;
|
||||
}
|
||||
|
||||
SavedSearch.type = 'search';
|
||||
|
@ -44,5 +46,9 @@ module.factory('SavedSearch', function (courier) {
|
|||
|
||||
SavedSearch.searchSource = true;
|
||||
|
||||
SavedSearch.prototype.getFullPath = function () {
|
||||
return `/app/kibana#/discover/${this.id}`;
|
||||
};
|
||||
|
||||
return SavedSearch;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,568 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`directories should not render directory entry when showOnHomePage is false 1`] = `
|
||||
<EuiPage
|
||||
className="home"
|
||||
>
|
||||
<AddData
|
||||
addBasePath={[Function]}
|
||||
isCloudEnabled={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Visualize and Explore Data
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Manage and Administer the Elastic Stack
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
Didn’t find what you were looking for?
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
type="button"
|
||||
>
|
||||
View full directory of Kibana plugins
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
`;
|
||||
|
||||
exports[`directories should render ADMIN directory entry in "Manage" panel 1`] = `
|
||||
<EuiPage
|
||||
className="home"
|
||||
>
|
||||
<AddData
|
||||
addBasePath={[Function]}
|
||||
isCloudEnabled={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Visualize and Explore Data
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Manage and Administer the Elastic Stack
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
key="index_patterns"
|
||||
style={
|
||||
Object {
|
||||
"minHeight": 64,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Synopsis
|
||||
description="Manage the index patterns that help retrieve your data from Elasticsearch."
|
||||
iconUrl="base_path//plugins/kibana/assets/app_index_pattern.svg"
|
||||
title="Index Patterns"
|
||||
url="base_path/index_management_landing_page"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
Didn’t find what you were looking for?
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
type="button"
|
||||
>
|
||||
View full directory of Kibana plugins
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
`;
|
||||
|
||||
exports[`directories should render DATA directory entry in "Explore Data" panel 1`] = `
|
||||
<EuiPage
|
||||
className="home"
|
||||
>
|
||||
<AddData
|
||||
addBasePath={[Function]}
|
||||
isCloudEnabled={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Visualize and Explore Data
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
key="dashboard"
|
||||
style={
|
||||
Object {
|
||||
"minHeight": 64,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Synopsis
|
||||
description="Display and share a collection of visualizations and saved searches."
|
||||
iconUrl="base_path//plugins/kibana/assets/app_dashboard.svg"
|
||||
title="Dashboard"
|
||||
url="base_path/dashboard_landing_page"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Manage and Administer the Elastic Stack
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
Didn’t find what you were looking for?
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
type="button"
|
||||
>
|
||||
View full directory of Kibana plugins
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
`;
|
||||
|
||||
exports[`should not contain RecentlyAccessed panel when there is no recentlyAccessed history 1`] = `
|
||||
<EuiPage
|
||||
className="home"
|
||||
>
|
||||
<AddData
|
||||
addBasePath={[Function]}
|
||||
isCloudEnabled={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Visualize and Explore Data
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Manage and Administer the Elastic Stack
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
Didn’t find what you were looking for?
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
type="button"
|
||||
>
|
||||
View full directory of Kibana plugins
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
`;
|
||||
|
||||
exports[`should render home component 1`] = `
|
||||
<EuiPage
|
||||
className="home"
|
||||
>
|
||||
<React.Fragment>
|
||||
<RecentlyAccessed
|
||||
recentlyAccessed={
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"label": "my vis",
|
||||
"link": "link_to_my_vis",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
</React.Fragment>
|
||||
<AddData
|
||||
addBasePath={[Function]}
|
||||
isCloudEnabled={true}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Visualize and Explore Data
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h3>
|
||||
Manage and Administer the Elastic Stack
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={2}
|
||||
gutterSize="l"
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
Didn’t find what you were looking for?
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
type="button"
|
||||
>
|
||||
View full directory of Kibana plugins
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPage>
|
||||
`;
|
|
@ -0,0 +1,109 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`render 1`] = `
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
>
|
||||
Recently viewed
|
||||
</EuiTextColor>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="flexEnd"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="spaceBetween"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="recentlyAccessedFlexItem"
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<React.Fragment
|
||||
key="0"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="recentlyAccessedItem"
|
||||
component="div"
|
||||
grow={false}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "3.9000000000000004em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiLink
|
||||
className="recentlyAccessedLongLink"
|
||||
color="primary"
|
||||
href="link0"
|
||||
type="button"
|
||||
>
|
||||
label0
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</React.Fragment>
|
||||
<React.Fragment
|
||||
key="1"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="recentlyAccessedSeparator"
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<EuiIcon
|
||||
color="subdued"
|
||||
size="m"
|
||||
type="dot"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
className="recentlyAccessedItem"
|
||||
component="div"
|
||||
grow={false}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "3.9000000000000004em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiLink
|
||||
className="recentlyAccessedLongLink"
|
||||
color="primary"
|
||||
href="link1"
|
||||
type="button"
|
||||
>
|
||||
label1
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</React.Fragment>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
`;
|
188
src/core_plugins/kibana/public/home/components/add_data.js
Normal file
188
src/core_plugins/kibana/public/home/components/add_data.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
KuiCardGroup,
|
||||
KuiCard,
|
||||
KuiCardDescription,
|
||||
KuiCardDescriptionTitle,
|
||||
KuiCardDescriptionText,
|
||||
KuiCardFooter,
|
||||
} from 'ui_framework/components';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export function AddData({ addBasePath, isCloudEnabled }) {
|
||||
|
||||
const renderCards = () => {
|
||||
const cardStyle = {
|
||||
width: '250px',
|
||||
'minWidth': '200px',
|
||||
'border': 'none'
|
||||
};
|
||||
|
||||
let apmCard;
|
||||
if (!isCloudEnabled) {
|
||||
apmCard = (
|
||||
<KuiCard style={cardStyle}>
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_apm.svg')}
|
||||
/>
|
||||
<p>
|
||||
APM
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
APM automatically collects in-depth performance metrics and errors from inside your applications.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<EuiButton
|
||||
href="#/home/tutorial/apm"
|
||||
>
|
||||
Add APM
|
||||
</EuiButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="kuiVerticalRhythm">
|
||||
<KuiCardGroup>
|
||||
|
||||
{apmCard}
|
||||
|
||||
<KuiCard style={cardStyle}>
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_logging.svg')}
|
||||
/>
|
||||
<p>
|
||||
Logging
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
Ingest logs from popular data sources and easily visualize in preconfigured dashboards.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<EuiButton
|
||||
href="#/home/tutorial_directory/logging"
|
||||
>
|
||||
Add log data
|
||||
</EuiButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
|
||||
<KuiCard style={cardStyle}>
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_monitoring.svg')}
|
||||
/>
|
||||
<p>
|
||||
Metrics
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
Collect metrics from the operating system and services running on your servers.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<EuiButton
|
||||
href="#/home/tutorial_directory/metrics"
|
||||
>
|
||||
Add metric data
|
||||
</EuiButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
|
||||
<KuiCard style={cardStyle}>
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_security.svg')}
|
||||
/>
|
||||
<p>
|
||||
Security analytics
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
Centralize security events for interactive investigation in ready-to-go visualizations.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<EuiButton
|
||||
href="#/home/tutorial_directory/security"
|
||||
>
|
||||
Add security events
|
||||
</EuiButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
</KuiCardGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h3>Add Data to Kibana</h3>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<p>
|
||||
Use these solutions to quickly turn your data into pre-built dashboards and monitoring systems.
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{renderCards()}
|
||||
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<span style={{ height: 38 }}>
|
||||
Data already in Elasticsearch?
|
||||
</span>
|
||||
<EuiLink
|
||||
style={{ marginLeft: 8 }}
|
||||
href="#/management/kibana/index"
|
||||
>
|
||||
Set up index patterns
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
AddData.propTypes = {
|
||||
addBasePath: PropTypes.func.isRequired,
|
||||
isCloudEnabled: PropTypes.bool.isRequired,
|
||||
};
|
|
@ -1,17 +1,11 @@
|
|||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Synopsis } from './synopsis';
|
||||
import {
|
||||
KuiLinkButton,
|
||||
KuiCardGroup,
|
||||
KuiCard,
|
||||
KuiCardDescription,
|
||||
KuiCardDescriptionTitle,
|
||||
KuiCardDescriptionText,
|
||||
KuiCardFooter,
|
||||
} from 'ui_framework/components';
|
||||
import { AddData } from './add_data';
|
||||
import { RecentlyAccessed, recentlyAccessedShape } from './recently_accessed';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiPage,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
|
@ -20,15 +14,11 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiFlexGrid,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
const kbnBaseUrl = chrome.getInjected('kbnBaseUrl');
|
||||
|
||||
export function Home({ addBasePath, directories, isCloudEnabled }) {
|
||||
export function Home({ addBasePath, directories, isCloudEnabled, recentlyAccessed }) {
|
||||
|
||||
const renderDirectories = (category) => {
|
||||
return directories
|
||||
|
@ -49,173 +39,31 @@ export function Home({ addBasePath, directories, isCloudEnabled }) {
|
|||
});
|
||||
};
|
||||
|
||||
const renderPromo = () => {
|
||||
const cardStyle = {
|
||||
width: '250px',
|
||||
'minWidth': '200px'
|
||||
};
|
||||
|
||||
let apmCard;
|
||||
if (!isCloudEnabled) {
|
||||
apmCard = (
|
||||
<KuiCard style={cardStyle} className="euiPanel">
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_apm.svg')}
|
||||
/>
|
||||
<p>
|
||||
APM
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
APM automatically collects in-depth performance metrics and errors from inside your applications.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<KuiLinkButton
|
||||
buttonType="secondary"
|
||||
href={addBasePath(`${kbnBaseUrl}#/home/tutorial/apm`)}
|
||||
>
|
||||
Add APM
|
||||
</KuiLinkButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="kuiVerticalRhythm">
|
||||
<KuiCardGroup>
|
||||
|
||||
{apmCard}
|
||||
|
||||
<KuiCard style={cardStyle} className="euiPanel">
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_logging.svg')}
|
||||
/>
|
||||
<p>
|
||||
Logging
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
Ingest logs from popular data sources and easily visualize in preconfigured dashboards.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<KuiLinkButton
|
||||
buttonType="secondary"
|
||||
href={addBasePath(`${kbnBaseUrl}#/home/tutorial_directory/logging`)}
|
||||
>
|
||||
Add log data
|
||||
</KuiLinkButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
|
||||
<KuiCard style={cardStyle} className="euiPanel">
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_monitoring.svg')}
|
||||
/>
|
||||
<p>
|
||||
Metrics
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
Collect metrics from the operating system and services running on your servers.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<KuiLinkButton
|
||||
buttonType="secondary"
|
||||
href={addBasePath(`${kbnBaseUrl}#/home/tutorial_directory/metrics`)}
|
||||
>
|
||||
Add metric data
|
||||
</KuiLinkButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
|
||||
<KuiCard style={cardStyle} className="euiPanel">
|
||||
<KuiCardDescription>
|
||||
<KuiCardDescriptionTitle>
|
||||
<img
|
||||
src={addBasePath('/plugins/kibana/assets/app_security.svg')}
|
||||
/>
|
||||
<p>
|
||||
Security analytics
|
||||
</p>
|
||||
</KuiCardDescriptionTitle>
|
||||
|
||||
<KuiCardDescriptionText>
|
||||
Centralize security events for interactive investigation in ready-to-go visualizations.
|
||||
</KuiCardDescriptionText>
|
||||
</KuiCardDescription>
|
||||
|
||||
<KuiCardFooter>
|
||||
<KuiLinkButton
|
||||
buttonType="secondary"
|
||||
href={addBasePath(`${kbnBaseUrl}#/home/tutorial_directory/security`)}
|
||||
>
|
||||
Add security events
|
||||
</KuiLinkButton>
|
||||
</KuiCardFooter>
|
||||
</KuiCard>
|
||||
</KuiCardGroup>
|
||||
</div>
|
||||
let recentlyAccessedPanel;
|
||||
if (recentlyAccessed.length > 0) {
|
||||
recentlyAccessedPanel = (
|
||||
<Fragment>
|
||||
<RecentlyAccessed
|
||||
recentlyAccessed={recentlyAccessed}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPage className="home">
|
||||
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="flexEnd"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="l">
|
||||
<h1>Add Data to Kibana</h1>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<p>
|
||||
Use these solutions to quickly turn your data into pre-built dashboards and monitoring systems.
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{recentlyAccessedPanel}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTextColor color="subdued">
|
||||
<EuiText>
|
||||
<p>
|
||||
Data already in Elasticsearch?
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiTextColor>
|
||||
<EuiSpacer size="s" />
|
||||
<a href="#/management/kibana/index" className="euiButton euiButton--primary euiButton--small">
|
||||
<span className="euiButton__content">
|
||||
Set up index patterns
|
||||
</span>
|
||||
</a>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
|
||||
{ renderPromo() }
|
||||
<AddData
|
||||
addBasePath={addBasePath}
|
||||
isCloudEnabled={isCloudEnabled}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup className="kuiVerticalRhythm">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiTitle>
|
||||
|
@ -254,12 +102,11 @@ export function Home({ addBasePath, directories, isCloudEnabled }) {
|
|||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<KuiLinkButton
|
||||
buttonType="secondary"
|
||||
<EuiButton
|
||||
href="#/home/feature_directory"
|
||||
>
|
||||
View full directory of Kibana plugins
|
||||
</KuiLinkButton>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
|
@ -279,4 +126,5 @@ Home.propTypes = {
|
|||
category: PropTypes.string.isRequired
|
||||
})),
|
||||
isCloudEnabled: PropTypes.bool.isRequired,
|
||||
recentlyAccessed: PropTypes.arrayOf(recentlyAccessedShape).isRequired,
|
||||
};
|
||||
|
|
95
src/core_plugins/kibana/public/home/components/home.test.js
Normal file
95
src/core_plugins/kibana/public/home/components/home.test.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Home } from './home';
|
||||
import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
|
||||
const addBasePath = (url) => { return `base_path/${url}`; };
|
||||
|
||||
test('should render home component', () => {
|
||||
const recentlyAccessed = [
|
||||
{
|
||||
label: 'my vis',
|
||||
link: 'link_to_my_vis',
|
||||
id: '1'
|
||||
}
|
||||
];
|
||||
const component = shallow(<Home
|
||||
addBasePath={addBasePath}
|
||||
directories={[]}
|
||||
isCloudEnabled={true}
|
||||
recentlyAccessed={recentlyAccessed}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('should not contain RecentlyAccessed panel when there is no recentlyAccessed history', () => {
|
||||
const component = shallow(<Home
|
||||
addBasePath={addBasePath}
|
||||
directories={[]}
|
||||
isCloudEnabled={true}
|
||||
recentlyAccessed={[]}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
describe('directories', () => {
|
||||
test('should render DATA directory entry in "Explore Data" panel', () => {
|
||||
const directoryEntry = {
|
||||
id: 'dashboard',
|
||||
title: 'Dashboard',
|
||||
description: 'Display and share a collection of visualizations and saved searches.',
|
||||
icon: '/plugins/kibana/assets/app_dashboard.svg',
|
||||
path: 'dashboard_landing_page',
|
||||
showOnHomePage: true,
|
||||
category: FeatureCatalogueCategory.DATA
|
||||
};
|
||||
|
||||
const component = shallow(<Home
|
||||
addBasePath={addBasePath}
|
||||
directories={[directoryEntry]}
|
||||
isCloudEnabled={true}
|
||||
recentlyAccessed={[]}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('should render ADMIN directory entry in "Manage" panel', () => {
|
||||
const directoryEntry = {
|
||||
id: 'index_patterns',
|
||||
title: 'Index Patterns',
|
||||
description: 'Manage the index patterns that help retrieve your data from Elasticsearch.',
|
||||
icon: '/plugins/kibana/assets/app_index_pattern.svg',
|
||||
path: 'index_management_landing_page',
|
||||
showOnHomePage: true,
|
||||
category: FeatureCatalogueCategory.ADMIN
|
||||
};
|
||||
|
||||
const component = shallow(<Home
|
||||
addBasePath={addBasePath}
|
||||
directories={[directoryEntry]}
|
||||
isCloudEnabled={true}
|
||||
recentlyAccessed={[]}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('should not render directory entry when showOnHomePage is false', () => {
|
||||
const directoryEntry = {
|
||||
id: 'management',
|
||||
title: 'Management',
|
||||
description: 'Your center console for managing the Elastic Stack.',
|
||||
icon: '/plugins/kibana/assets/app_management.svg',
|
||||
path: 'management_landing_page',
|
||||
showOnHomePage: false,
|
||||
category: FeatureCatalogueCategory.ADMIN
|
||||
};
|
||||
|
||||
const component = shallow(<Home
|
||||
addBasePath={addBasePath}
|
||||
directories={[directoryEntry]}
|
||||
isCloudEnabled={true}
|
||||
recentlyAccessed={[]}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
});
|
|
@ -12,8 +12,9 @@ import {
|
|||
import { getTutorial } from '../load_tutorials';
|
||||
import { replaceTemplateStrings } from './tutorial/replace_template_strings';
|
||||
import chrome from 'ui/chrome';
|
||||
import { recentlyAccessedShape } from './recently_accessed';
|
||||
|
||||
export function HomeApp({ addBasePath, directories }) {
|
||||
export function HomeApp({ addBasePath, directories, recentlyAccessed }) {
|
||||
|
||||
const isCloudEnabled = chrome.getInjected('isCloudEnabled', false);
|
||||
|
||||
|
@ -65,6 +66,7 @@ export function HomeApp({ addBasePath, directories }) {
|
|||
addBasePath={addBasePath}
|
||||
directories={directories}
|
||||
isCloudEnabled={isCloudEnabled}
|
||||
recentlyAccessed={recentlyAccessed}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
|
@ -82,5 +84,6 @@ HomeApp.propTypes = {
|
|||
path: PropTypes.string.isRequired,
|
||||
showOnHomePage: PropTypes.bool.isRequired,
|
||||
category: PropTypes.string.isRequired
|
||||
}))
|
||||
})),
|
||||
recentlyAccessed: PropTypes.arrayOf(recentlyAccessedShape).isRequired,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
import './recently_accessed.less';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export const NUM_LONG_LINKS = 5;
|
||||
|
||||
export class RecentlyAccessed extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
onButtonClick = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
|
||||
closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
renderDropdown = () => {
|
||||
if (this.props.recentlyAccessed.length <= NUM_LONG_LINKS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dropdownLinks = [];
|
||||
for (let i = NUM_LONG_LINKS; i < this.props.recentlyAccessed.length; i++) {
|
||||
dropdownLinks.push(
|
||||
(
|
||||
<li
|
||||
style={{ marginBottom: 8 }}
|
||||
key={this.props.recentlyAccessed[i].id}
|
||||
data-test-subj={`moreRecentlyAccessedItem${this.props.recentlyAccessed[i].id}`}
|
||||
>
|
||||
<EuiLink
|
||||
className="recentlyAccessedDropwdownLink"
|
||||
href={this.props.recentlyAccessed[i].link}
|
||||
>
|
||||
{this.props.recentlyAccessed[i].label}
|
||||
</EuiLink>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const openPopoverComponent = (
|
||||
<EuiLink
|
||||
onClick={this.onButtonClick}
|
||||
data-test-subj="openMoreRecentlyAccessedPopover"
|
||||
>
|
||||
<EuiTextColor
|
||||
className="recentlyAccessedDropdownLabel"
|
||||
color="subdued"
|
||||
>
|
||||
{`${dropdownLinks.length} more`}
|
||||
</EuiTextColor>
|
||||
<EuiIcon
|
||||
type="arrowDown"
|
||||
color="subdued"
|
||||
/>
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
let anchorPosition = 'downRight';
|
||||
if (window.innerWidth <= 768) {
|
||||
anchorPosition = 'downLeft';
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="popover"
|
||||
ownFocus
|
||||
button={openPopoverComponent}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
anchorPosition={anchorPosition}
|
||||
>
|
||||
<ul>
|
||||
{dropdownLinks}
|
||||
</ul>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
renderLongLink = (recentlyAccessedItem, includeSeparator = false) => {
|
||||
let separator;
|
||||
if (includeSeparator) {
|
||||
separator = (
|
||||
<EuiFlexItem grow={false} className="recentlyAccessedSeparator">
|
||||
<EuiText>
|
||||
<EuiIcon
|
||||
type="dot"
|
||||
color="subdued"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
// Want to avoid a bunch of white space around items with short labels (happens when min width is too large).
|
||||
// Also want to avoid truncating really short names (happens when there is no min width)
|
||||
// Dynamically setting the min width based on label lengh meets both of these goals.
|
||||
const EM_RATIO = 0.65; // 'em' ratio that avoids too much horizontal white space and too much truncation
|
||||
const minWidth = (recentlyAccessedItem.label.length < 8 ? recentlyAccessedItem.label.length : 8) * EM_RATIO;
|
||||
const style = { minWidth: `${minWidth}em` };
|
||||
return (
|
||||
<React.Fragment key={recentlyAccessedItem.id}>
|
||||
{separator}
|
||||
<EuiFlexItem
|
||||
className="recentlyAccessedItem"
|
||||
style={style}
|
||||
grow={false}
|
||||
>
|
||||
<EuiLink
|
||||
className="recentlyAccessedLongLink"
|
||||
href={recentlyAccessedItem.link}
|
||||
>
|
||||
{recentlyAccessedItem.label}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderRecentlyAccessed = () => {
|
||||
if (this.props.recentlyAccessed.length <= NUM_LONG_LINKS) {
|
||||
return this.props.recentlyAccessed.map((item, index) => {
|
||||
let includeSeparator = true;
|
||||
if (index === 0) {
|
||||
includeSeparator = false;
|
||||
}
|
||||
return this.renderLongLink(item, includeSeparator);
|
||||
});
|
||||
}
|
||||
|
||||
const links = [];
|
||||
for (let i = 0; i < NUM_LONG_LINKS; i++) {
|
||||
let includeSeparator = true;
|
||||
if (i === 0) {
|
||||
includeSeparator = false;
|
||||
}
|
||||
links.push(this.renderLongLink(
|
||||
this.props.recentlyAccessed[i],
|
||||
includeSeparator));
|
||||
}
|
||||
return links;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiTextColor color="subdued">
|
||||
Recently viewed
|
||||
</EuiTextColor>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s"/>
|
||||
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexEnd">
|
||||
<EuiFlexItem grow={false} className="recentlyAccessedFlexItem">
|
||||
<EuiFlexGroup>
|
||||
{this.renderRecentlyAccessed()}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
{this.renderDropdown()}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const recentlyAccessedShape = PropTypes.shape({
|
||||
label: PropTypes.string.isRequired,
|
||||
link: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
RecentlyAccessed.propTypes = {
|
||||
recentlyAccessed: PropTypes.arrayOf(recentlyAccessedShape).isRequired
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
@media only screen and (max-width: 768px) {
|
||||
.recentlyAccessedSeparator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.recentlyAccessedItem {
|
||||
overflow: hidden;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.recentlyAccessedLongLink {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.recentlyAccessedFlexItem {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.recentlyAccessedDropwdownLink {
|
||||
white-space: nowrap;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.recentlyAccessedDropdownLabel {
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { RecentlyAccessed, NUM_LONG_LINKS } from './recently_accessed';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
const createRecentlyAccessed = (length) => {
|
||||
const recentlyAccessed = [];
|
||||
let i = 0;
|
||||
while(recentlyAccessed.length < length) {
|
||||
recentlyAccessed.push({
|
||||
label: `label${recentlyAccessed.length}`,
|
||||
link: `link${recentlyAccessed.length}`,
|
||||
id: `${i++}`
|
||||
});
|
||||
}
|
||||
return recentlyAccessed;
|
||||
};
|
||||
|
||||
test('render', () => {
|
||||
const component = shallow(<RecentlyAccessed
|
||||
recentlyAccessed={createRecentlyAccessed(2)}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
describe('more popover', () => {
|
||||
test('should not be rendered when recently accessed list size is less than NUM_LONG_LINKS', () => {
|
||||
const component = mount(<RecentlyAccessed
|
||||
recentlyAccessed={createRecentlyAccessed(NUM_LONG_LINKS - 1)}
|
||||
/>);
|
||||
|
||||
const moreRecentlyAccessed = findTestSubject(component, 'openMoreRecentlyAccessedPopover');
|
||||
expect(moreRecentlyAccessed.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should not be rendered when recently accessed list size is NUM_LONG_LINKS', () => {
|
||||
const component = mount(<RecentlyAccessed
|
||||
recentlyAccessed={createRecentlyAccessed(NUM_LONG_LINKS)}
|
||||
/>);
|
||||
|
||||
const moreRecentlyAccessed = findTestSubject(component, 'openMoreRecentlyAccessedPopover');
|
||||
expect(moreRecentlyAccessed.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('recently accessed list size exceeds NUM_LONG_LINKS', () => {
|
||||
test('should be rendered', () => {
|
||||
const component = mount(<RecentlyAccessed
|
||||
recentlyAccessed={createRecentlyAccessed(NUM_LONG_LINKS + 1)}
|
||||
/>);
|
||||
|
||||
const moreRecentlyAccessed = findTestSubject(component, 'openMoreRecentlyAccessedPopover');
|
||||
expect(moreRecentlyAccessed.length).toBe(1);
|
||||
});
|
||||
|
||||
test('should only contain overflow recently accessed items when opened', () => {
|
||||
const numberOfRecentlyAccessed = NUM_LONG_LINKS + 2;
|
||||
const component = mount(<RecentlyAccessed
|
||||
recentlyAccessed={createRecentlyAccessed(numberOfRecentlyAccessed)}
|
||||
/>);
|
||||
|
||||
const moreRecentlyAccessed = findTestSubject(component, 'openMoreRecentlyAccessedPopover');
|
||||
moreRecentlyAccessed.simulate('click');
|
||||
|
||||
let i = 0;
|
||||
while (i < numberOfRecentlyAccessed) {
|
||||
const item = findTestSubject(component, `moreRecentlyAccessedItem${i}`);
|
||||
if (i < NUM_LONG_LINKS) {
|
||||
expect(item.length).toBe(0);
|
||||
} else {
|
||||
expect(item.length).toBe(1);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
<home-app
|
||||
add-base-path="addBasePath"
|
||||
directories="directories"
|
||||
recently-accessed="recentlyAccessed"
|
||||
/>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { uiModules } from 'ui/modules';
|
|||
import {
|
||||
HomeApp
|
||||
} from './components/home_app';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
|
||||
const app = uiModules.get('apps/home', []);
|
||||
app.directive('homeApp', function (reactDirective) {
|
||||
|
@ -19,6 +20,10 @@ function getRoute() {
|
|||
controller($scope, Private) {
|
||||
$scope.addBasePath = chrome.addBasePath;
|
||||
$scope.directories = Private(FeatureCatalogueRegistryProvider).inTitleOrder;
|
||||
$scope.recentlyAccessed = recentlyAccessed.get().map(item => {
|
||||
item.link = chrome.addBasePath(item.link);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { VisualizeConstants } from '../visualize_constants';
|
|||
import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
|
||||
import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
|
||||
import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
|
||||
uiRoutes
|
||||
.when(VisualizeConstants.CREATE_PATH, {
|
||||
|
@ -47,6 +48,13 @@ uiRoutes
|
|||
resolve: {
|
||||
savedVis: function (savedVisualizations, courier, $route) {
|
||||
return savedVisualizations.get($route.current.params.id)
|
||||
.then((savedVis) => {
|
||||
recentlyAccessed.add(
|
||||
savedVis.getFullPath(),
|
||||
savedVis.title,
|
||||
savedVis.id);
|
||||
return savedVis;
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'visualization': '/visualize',
|
||||
'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
|
|
|
@ -10,6 +10,7 @@ import _ from 'lodash';
|
|||
import { VisProvider } from 'ui/vis';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { updateOldState } from 'ui/vis/vis_update_state';
|
||||
import { VisualizeConstants } from 'plugins/kibana/visualize/visualize_constants';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
|
@ -45,6 +46,8 @@ uiModules
|
|||
|
||||
afterESResp: this._afterEsResp
|
||||
});
|
||||
|
||||
this.showInRecenltyAccessed = true;
|
||||
}
|
||||
|
||||
SavedVis.type = 'visualization';
|
||||
|
@ -63,6 +66,10 @@ uiModules
|
|||
|
||||
SavedVis.searchSource = true;
|
||||
|
||||
SavedVis.prototype.getFullPath = function () {
|
||||
return `/app/kibana#${VisualizeConstants.EDIT_PATH}/${this.id}`;
|
||||
};
|
||||
|
||||
SavedVis.prototype._afterEsResp = function () {
|
||||
const self = this;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { DocTitleProvider } from 'ui/doc_title';
|
|||
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
|
||||
import { notify, fatalError, toastNotifications } from 'ui/notify';
|
||||
import { timezoneProvider } from 'ui/vis/lib/timezone';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
|
||||
require('ui/autoload/all');
|
||||
require('plugins/timelion/directives/cells/cells');
|
||||
|
@ -18,8 +19,6 @@ require('plugins/timelion/app.less');
|
|||
|
||||
document.title = 'Timelion - Kibana';
|
||||
|
||||
require('ui/chrome');
|
||||
|
||||
const app = require('ui/modules').get('apps/timelion', []);
|
||||
|
||||
require('plugins/timelion/services/saved_sheets');
|
||||
|
@ -40,6 +39,15 @@ require('ui/routes')
|
|||
resolve: {
|
||||
savedSheet: function (courier, savedSheets, $route) {
|
||||
return savedSheets.get($route.current.params.id)
|
||||
.then((savedSheet) => {
|
||||
if ($route.current.params.id) {
|
||||
recentlyAccessed.add(
|
||||
savedSheet.getFullPath(),
|
||||
savedSheet.title,
|
||||
savedSheet.id);
|
||||
}
|
||||
return savedSheet;
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'search': '/'
|
||||
}));
|
||||
|
|
|
@ -30,6 +30,8 @@ module.factory('SavedSheet', function (courier, config) {
|
|||
version: 1,
|
||||
}
|
||||
});
|
||||
|
||||
this.showInRecenltyAccessed = true;
|
||||
}
|
||||
|
||||
// save these objects with the 'sheet' type
|
||||
|
@ -52,5 +54,9 @@ module.factory('SavedSheet', function (courier, config) {
|
|||
// Order these fields to the top, the rest are alphabetical
|
||||
SavedSheet.fieldOrder = ['title', 'description'];
|
||||
|
||||
SavedSheet.prototype.getFullPath = function () {
|
||||
return `/app/timelion#/${this.id}`;
|
||||
};
|
||||
|
||||
return SavedSheet;
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import MappingSetupProvider from 'ui/utils/mapping_setup';
|
|||
import { SearchSourceProvider } from '../data_source/search_source';
|
||||
import { SavedObjectsClientProvider, findObjectByTitle } from 'ui/saved_objects';
|
||||
import { migrateLegacyQuery } from '../../utils/migrateLegacyQuery.js';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
|
||||
/**
|
||||
* An error message to be used when the user rejects a confirm overwrite.
|
||||
|
@ -344,6 +345,9 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm
|
|||
this.id = resp.id;
|
||||
})
|
||||
.then(() => {
|
||||
if (this.showInRecenltyAccessed && this.getFullPath) {
|
||||
recentlyAccessed.add(this.getFullPath(), this.title, this.id);
|
||||
}
|
||||
this.isSaving = false;
|
||||
this.lastSavedTitle = this.title;
|
||||
return this.id;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import './persisted_log';
|
||||
|
||||
export { PersistedLog } from './persisted_log';
|
||||
export { recentlyAccessed } from './recently_accessed';
|
||||
|
|
|
@ -4,11 +4,16 @@ import { Storage } from 'ui/storage';
|
|||
|
||||
const localStorage = new Storage(window.localStorage);
|
||||
|
||||
const defaultIsDuplicate = (oldItem, newItem) => {
|
||||
return _.isEqual(oldItem, newItem);
|
||||
};
|
||||
|
||||
export class PersistedLog {
|
||||
constructor(name, options = {}, storage = localStorage) {
|
||||
this.name = name;
|
||||
this.maxLength = parseInt(options.maxLength, 10);
|
||||
this.filterDuplicates = options.filterDuplicates || false;
|
||||
this.isDuplicate = options.isDuplicate || defaultIsDuplicate;
|
||||
this.storage = storage;
|
||||
this.items = this.storage.get(this.name) || [];
|
||||
if (!isNaN(this.maxLength)) this.items = _.take(this.items, this.maxLength);
|
||||
|
@ -21,8 +26,8 @@ export class PersistedLog {
|
|||
|
||||
// remove any matching items from the stack if option is set
|
||||
if (this.filterDuplicates) {
|
||||
_.remove(this.items, function (item) {
|
||||
return _.isEqual(item, val);
|
||||
_.remove(this.items, (item) => {
|
||||
return this.isDuplicate(item, val);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
29
src/ui/public/persisted_log/recently_accessed.js
Normal file
29
src/ui/public/persisted_log/recently_accessed.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { PersistedLog } from 'ui/persisted_log';
|
||||
|
||||
class RecentlyAccessed {
|
||||
constructor() {
|
||||
const historyOptions = {
|
||||
maxLength: 20,
|
||||
filterDuplicates: true,
|
||||
isDuplicate: (oldItem, newItem) => {
|
||||
return oldItem.id === newItem.id;
|
||||
}
|
||||
};
|
||||
this.history = new PersistedLog('kibana.history.recentlyAccessed', historyOptions);
|
||||
}
|
||||
|
||||
add(link, label, id) {
|
||||
const historyItem = {
|
||||
link: link,
|
||||
label: label,
|
||||
id: id
|
||||
};
|
||||
this.history.add(historyItem);
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.history.get();
|
||||
}
|
||||
}
|
||||
|
||||
export const recentlyAccessed = new RecentlyAccessed();
|
|
@ -3684,9 +3684,9 @@ enzyme-adapter-utils@^1.3.0:
|
|||
object.assign "^4.0.4"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
enzyme-to-json@3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.1.4.tgz#a4a85a8f7b561cb8c9c0d728ad1b619a3fed7df2"
|
||||
enzyme-to-json@3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.0.tgz#553e23a09ffb4b0cf09287e2edf9c6539fddaa84"
|
||||
dependencies:
|
||||
lodash "^4.17.4"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue