diff --git a/src/core_plugins/getting_started/index.js b/src/core_plugins/getting_started/index.js new file mode 100644 index 0000000000000..61dd97ba56408 --- /dev/null +++ b/src/core_plugins/getting_started/index.js @@ -0,0 +1,8 @@ +export default function (kibana) { + + return new kibana.Plugin({ + uiExports: { + managementSections: ['plugins/getting_started'] + } + }); +} diff --git a/src/core_plugins/getting_started/package.json b/src/core_plugins/getting_started/package.json new file mode 100644 index 0000000000000..da2e9c14b8d29 --- /dev/null +++ b/src/core_plugins/getting_started/package.json @@ -0,0 +1,4 @@ +{ + "name": "getting_started", + "version": "kibana" +} diff --git a/src/core_plugins/getting_started/public/components/getting_started/getting_started.html b/src/core_plugins/getting_started/public/components/getting_started/getting_started.html new file mode 100644 index 0000000000000..c9324a16cdc8c --- /dev/null +++ b/src/core_plugins/getting_started/public/components/getting_started/getting_started.html @@ -0,0 +1,176 @@ +
+
+

+ Welcome to Kibana +

+
+ + + +
+ +
+ +
+
+
+
+
+

+ First, add your data +

+
+ +
+
+ + +
+
+
+ + +
+ +
+
+
+

+ Visualize and explore +

+
+ +
+
+ +
+
+
+ + +
+ +
+
+
+

+ Manage and monitor +

+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+ +
+ +
+

+ Just want to see what Kibana is capable of doing? + View the demo site. +

+
+ +
+

+ Kibana documentation + is always available to help. +

+
+
diff --git a/src/core_plugins/getting_started/public/components/getting_started/getting_started.js b/src/core_plugins/getting_started/public/components/getting_started/getting_started.js new file mode 100644 index 0000000000000..14de2353076aa --- /dev/null +++ b/src/core_plugins/getting_started/public/components/getting_started/getting_started.js @@ -0,0 +1,58 @@ +import { uiModules } from 'ui/modules'; +import 'ui/getting_started/opt_out_directive'; +import { GettingStartedRegistryProvider } from 'ui/getting_started/registry'; +import { GETTING_STARTED_REGISTRY_TYPES } from 'ui/getting_started/constants'; +import { hasOptedOutOfGettingStarted } from 'ui/getting_started/opt_out_helpers'; +import { documentationLinks } from 'ui/documentation_links'; + +import kibanaLogo from 'ui/images/logo-kibana-small.svg'; +import beatsLogo from 'ui/images/logo-beats-small.svg'; +import logstashLogo from 'ui/images/logo-logstash-small.svg'; +import dashboardIcon from 'ui/images/icon-dashboard.svg'; +import shieldIcon from 'ui/images/icon-shield.svg'; + +import template from './getting_started.html'; +import './getting_started.less'; +import '../injected_items'; + +const app = uiModules.get('kibana'); + +app.directive('gettingStarted', function ($injector) { + const Private = $injector.get('Private'); + + const registry = Private(GettingStartedRegistryProvider); + + return { + restrict: 'E', + template: template, + scope: { + }, + bindToController: true, + controllerAs: 'gettingStarted', + controller: class GettingStartedController { + constructor() { + const registeredTopMessages = registry.byType[GETTING_STARTED_REGISTRY_TYPES.TOP_MESSAGE] || []; + this.topMessages = registeredTopMessages.map(item => item.template); + + const registeredManageAndMonitorMessages = registry.byType[GETTING_STARTED_REGISTRY_TYPES.MANAGE_AND_MONITOR_MESSAGE] || []; + this.manageAndMonitorMessages = registeredManageAndMonitorMessages.map(item => item.template); + + this.imageUrls = { + kibanaLogo, + beatsLogo, + logstashLogo, + dashboardIcon, + shieldIcon + }; + + this.documentationLinks = documentationLinks; + } + + hasManageAndMonitorMessages = () => { + return this.manageAndMonitorMessages.length > 0; + } + + hasOptedOut = hasOptedOutOfGettingStarted; + } + }; +}); diff --git a/src/core_plugins/getting_started/public/components/getting_started/getting_started.less b/src/core_plugins/getting_started/public/components/getting_started/getting_started.less new file mode 100644 index 0000000000000..777b394cfec0f --- /dev/null +++ b/src/core_plugins/getting_started/public/components/getting_started/getting_started.less @@ -0,0 +1,59 @@ +.gettingStartedContent { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 0; +} + +.gettingStartedWelcome { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 200px; + width: 100%; + background-color: #f7f7f7; +} + +.gettingStartedTitle { + transform: translateY(10px); +} + +.gettingStartedLogo { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + border: 1px solid #D7DBDD; + width: 100px; + height: 100px; + border-radius: 100%; + + background-color: #ffffff; + + transform: translateY(-60px); +} + +.gettingStartedLogoRow { + display: flex; + flex-direction: row; + justify-content: center; +} + +.gettingStartedLogoRow__logo { + height: 80px; + + & + & { + margin-left: 40px; + } +} + +.gettingStartedCard__descriptionText { + margin-top: 32px; + margin-bottom: 32px; +} + +.gettingStartedCard { + width: 340px; +} \ No newline at end of file diff --git a/src/core_plugins/getting_started/public/components/getting_started/index.js b/src/core_plugins/getting_started/public/components/getting_started/index.js new file mode 100644 index 0000000000000..730b88e595f36 --- /dev/null +++ b/src/core_plugins/getting_started/public/components/getting_started/index.js @@ -0,0 +1 @@ +import './getting_started'; \ No newline at end of file diff --git a/src/core_plugins/getting_started/public/components/injected_items/index.js b/src/core_plugins/getting_started/public/components/injected_items/index.js new file mode 100644 index 0000000000000..3f4523d33f076 --- /dev/null +++ b/src/core_plugins/getting_started/public/components/injected_items/index.js @@ -0,0 +1 @@ +import './injected_items'; diff --git a/src/core_plugins/getting_started/public/components/injected_items/injected_items.html b/src/core_plugins/getting_started/public/components/injected_items/injected_items.html new file mode 100644 index 0000000000000..7c89b545c5ac0 --- /dev/null +++ b/src/core_plugins/getting_started/public/components/injected_items/injected_items.html @@ -0,0 +1 @@ +
diff --git a/src/core_plugins/getting_started/public/components/injected_items/injected_items.js b/src/core_plugins/getting_started/public/components/injected_items/injected_items.js new file mode 100644 index 0000000000000..97260591a1c93 --- /dev/null +++ b/src/core_plugins/getting_started/public/components/injected_items/injected_items.js @@ -0,0 +1,38 @@ +import { isArray } from 'lodash'; +import { uiModules } from 'ui/modules'; +import angular from 'angular'; + +import template from './injected_items.html'; +import './injected_items.less'; + +function makeAngularParseableExpression(item) { + return `
${item} 
`; +} + +const app = uiModules.get('kibana'); + +app.directive('injectedItems', function ($injector) { + const $compile = $injector.get('$compile'); + + return { + restrict: 'E', + replace: true, + template: template, + scope: { + items: '=' + }, + link: ($scope, $el) => { + const items = $scope.items; + + if (isArray(items) && items.length > 0) { + items.forEach(item => { + // Compile itemHtml with current $scope and append it into the container DOM element. + // We do this because we want to dynamically inject content (strings) into the DOM. This content + // may contain Angular directives so it must first be $compiled with the current $scope. + const itemHtml = $compile(makeAngularParseableExpression(item))($scope); + angular.element($el).append(itemHtml); + }); + } + } + }; +}); diff --git a/src/core_plugins/getting_started/public/components/injected_items/injected_items.less b/src/core_plugins/getting_started/public/components/injected_items/injected_items.less new file mode 100644 index 0000000000000..3102c5057d6fa --- /dev/null +++ b/src/core_plugins/getting_started/public/components/injected_items/injected_items.less @@ -0,0 +1,3 @@ +.injectedItems__item { + display: inline; +} diff --git a/src/core_plugins/getting_started/public/getting_started_route.html b/src/core_plugins/getting_started/public/getting_started_route.html new file mode 100644 index 0000000000000..870a1824b3790 --- /dev/null +++ b/src/core_plugins/getting_started/public/getting_started_route.html @@ -0,0 +1 @@ + diff --git a/src/core_plugins/getting_started/public/getting_started_route.js b/src/core_plugins/getting_started/public/getting_started_route.js new file mode 100644 index 0000000000000..46db8edf18ee8 --- /dev/null +++ b/src/core_plugins/getting_started/public/getting_started_route.js @@ -0,0 +1,14 @@ +import routes from 'ui/routes'; +import template from './getting_started_route.html'; +import './components/getting_started'; +import { GETTING_STARTED_ROUTE } from './lib/constants'; + +routes +.when(GETTING_STARTED_ROUTE, { + template: template, + controllerAs: 'gettingStartedRoute', + controller: class GettingStartedRouteController { + constructor() { + } + } +}); diff --git a/src/core_plugins/getting_started/public/index.js b/src/core_plugins/getting_started/public/index.js new file mode 100644 index 0000000000000..7ae2fd452e941 --- /dev/null +++ b/src/core_plugins/getting_started/public/index.js @@ -0,0 +1,3 @@ +import './lib/add_setup_work'; +import './lib/register_management_section'; +import './getting_started_route'; diff --git a/src/core_plugins/getting_started/public/lib/__tests__/add_setup_work.js b/src/core_plugins/getting_started/public/lib/__tests__/add_setup_work.js new file mode 100644 index 0000000000000..9ceabb222d12c --- /dev/null +++ b/src/core_plugins/getting_started/public/lib/__tests__/add_setup_work.js @@ -0,0 +1,189 @@ +import expect from 'expect.js'; +import sinon from 'sinon'; +import { set } from 'lodash'; +import uiChrome from 'ui/chrome'; +import { Notifier } from 'ui/notify/notifier'; +import { WAIT_FOR_URL_CHANGE_TOKEN } from 'ui/routes'; +import { gettingStartedGateCheck } from '../add_setup_work'; +import { + GETTING_STARTED_ROUTE, + CREATE_INDEX_PATTERN_ROUTE +} from '../constants'; +import { + hasOptedOutOfGettingStarted, + undoOptOutOfGettingStarted +} from 'ui/getting_started/opt_out_helpers'; + +describe('Getting Started page', () => { + describe('add_setup_work', () => { + describe('gettingStartedGateCheck', () => { + + let getIds; + let kbnUrl; + let config; + let $route; + + beforeEach(() => { + kbnUrl = { + change: sinon.spy() + }; + $route = {}; + set($route, 'current.$$route', {}); + }); + + describe('if the user is on an embedded page', () => { + beforeEach(() => { + set($route, 'current.params.embed', true); + }); + + it('should not show the UI chrome', () => { + expect(uiChrome.getVisible()).to.be(false); + }); + }); + + describe('if index patterns exist', () => { + beforeEach(() => { + config = { + get: sinon.stub(), + set: sinon.spy() + }; + getIds = sinon.stub() + .returns(Promise.resolve([ 'logstash-*', 'cars' ])); + }); + + it('sets the chrome to visible', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(uiChrome.getVisible()).to.be(true); + }); + + it('opts the user out of the Getting Started page', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(hasOptedOutOfGettingStarted()).to.be(true); + }); + + describe('if the current route does not require a default index pattern', () => { + beforeEach(() => { + $route.current.$$route.requireDefaultIndex = false; + }); + + it('returns without performing any default index pattern checks', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(config.get.called).to.be(false); + expect(config.set.called).to.be(false); + }); + }); + + describe('if the current route requires a default index pattern', () => { + beforeEach(() => { + set($route, 'current.$$route.requireDefaultIndex', true); + }); + + describe('if a default index pattern exists', () => { + beforeEach(() => { + config.get + .withArgs('defaultIndex') + .returns('an-index-pattern'); + }); + + it('returns without setting a default index pattern', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(config.set.called).to.be(false); + }); + }); + + describe('if a default index pattern does not exist', () => { + beforeEach(() => { + config.get + .withArgs('defaultIndex') + .returns(undefined); + }); + + it('sets the first index pattern as the default index pattern', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(config.set.calledWith('defaultIndex', 'logstash-*')).to.be(true); + }); + }); + }); + }); + + describe('if no index patterns exist', () => { + beforeEach(() => { + getIds = sinon.stub() + .returns(Promise.resolve([])); + }); + + describe('if user has opted out of the Getting Started page', () => { + it('sets the chrome to visible', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(uiChrome.getVisible()).to.be(true); + }); + + describe('if the current route does not require a default index pattern', () => { + beforeEach(() => { + $route.current.$$route.requireDefaultIndex = false; + }); + + it('returns without redirecting the user', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(kbnUrl.change.called).to.be(false); + }); + }); + + describe('if the current route requires a default index pattern', () => { + beforeEach(() => { + $route.current.$$route.requireDefaultIndex = true; + }); + + afterEach(() => { + // Clear out any notifications + Notifier.prototype._notifs.length = 0; + }); + + it('redirects the user to the Create Index Pattern page', async () => { + try { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + } catch (e) { + expect(e).to.be(WAIT_FOR_URL_CHANGE_TOKEN); + } + expect(kbnUrl.change.calledWith(CREATE_INDEX_PATTERN_ROUTE)).to.be(true); + }); + }); + }); + + describe('if the user has not opted out of the Getting Started page', () => { + beforeEach(() => { + undoOptOutOfGettingStarted(); + getIds = sinon.stub() + .returns(Promise.resolve([])); + }); + + describe('if the user is not already on Getting Started page', () => { + beforeEach(() => { + $route.current.$$route.originalPath = 'discover'; + }); + + it('redirects the user to the Getting Started page', async () => { + try { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + } catch (e) { + expect(e).to.be(WAIT_FOR_URL_CHANGE_TOKEN); + } + expect(kbnUrl.change.calledWith(GETTING_STARTED_ROUTE)).to.be(true); + }); + }); + + describe('if the user is already on Getting Started page', () => { + beforeEach(() => { + $route.current.$$route.originalPath = GETTING_STARTED_ROUTE; + }); + + it('redirects the user to the Getting Started page', async () => { + await gettingStartedGateCheck(getIds, kbnUrl, config, $route); + expect(kbnUrl.change.called).to.be(false); + }); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/core_plugins/getting_started/public/lib/add_setup_work.js b/src/core_plugins/getting_started/public/lib/add_setup_work.js new file mode 100644 index 0000000000000..d1be97d86015c --- /dev/null +++ b/src/core_plugins/getting_started/public/lib/add_setup_work.js @@ -0,0 +1,109 @@ +import { get } from 'lodash'; +import uiRoutes, { WAIT_FOR_URL_CHANGE_TOKEN } from 'ui/routes'; +import uiChrome from 'ui/chrome'; +import { Notifier } from 'ui/notify/notifier'; +import { IndexPatternsGetIdsProvider } from 'ui/index_patterns/_get_ids'; +import KbnUrlProvider from 'ui/url'; +import { hasOptedOutOfGettingStarted, optOutOfGettingStarted } from 'ui/getting_started/opt_out_helpers'; + +import { + GETTING_STARTED_ROUTE, + CREATE_INDEX_PATTERN_ROUTE +} from './constants'; + +function handleExistingIndexPatternsScenario(indexPatterns, currentRoute, config) { + // If index patterns exist, we're not going to show the user the Getting Started page. + // So we can show the chrome again at this point. + uiChrome.setVisible(true); + + // The user need not see the Getting Started page, so opt them out of it + optOutOfGettingStarted(); + + // Some routes require a default index pattern to be present. If we're + // NOT on such a route, there's nothing more to do; send the user on their way + if (!currentRoute.requireDefaultIndex) { + return; + } + + // Otherwise, check if we have a default index pattern + let defaultIndexPattern = config.get('defaultIndex'); + + // If we don't have an default index pattern, make the first index pattern the + // default one + if (!Boolean(defaultIndexPattern)) { + defaultIndexPattern = indexPatterns[0]; + config.set('defaultIndex', defaultIndexPattern); + } + + // At this point, we have a default index pattern and are all set! + return; +} + +function handleGettingStartedOptedOutScenario(currentRoute, kbnUrl) { + // If the user has opted out of the Getting Started page, we're not going to show them that page. + // So we can show the chrome again at this point. + uiChrome.setVisible(true); + + // Some routes require a default index pattern to be present. If we're + // NOT on such a route, there's nothing more to do; send the user on their way + if (!currentRoute.requireDefaultIndex) { + return; + } + + // Otherwise, redirect the user to the index pattern creation page with + // a notification about creating an index pattern + const notify = new Notifier({ + location: 'Index Patterns' + }); + notify.error('Please create a new index pattern'); + + kbnUrl.change(CREATE_INDEX_PATTERN_ROUTE); + throw WAIT_FOR_URL_CHANGE_TOKEN; +} + +function showGettingStartedPage(kbnUrl, isOnGettingStartedPage) { + // Redirect the user to the Getting Started page (unless they are on it already) + if (!isOnGettingStartedPage) { + kbnUrl.change(GETTING_STARTED_ROUTE); + throw WAIT_FOR_URL_CHANGE_TOKEN; + } +} + +/* + * This function is exported for unit testing + */ +export function gettingStartedGateCheck(getIds, kbnUrl, config, $route) { + const currentRoute = get($route, 'current.$$route'); + const isOnGettingStartedPage = get(currentRoute, 'originalPath') === GETTING_STARTED_ROUTE; + const isOnEmbeddedPage = Boolean(get($route, 'current.params.embed', false)); + + if (isOnEmbeddedPage) { + return Promise.resolve(); + } + + return getIds() + .then(indexPatterns => { + const indexPatternsExist = Array.isArray(indexPatterns) && indexPatterns.length > 0; + + if (indexPatternsExist) { + return handleExistingIndexPatternsScenario(indexPatterns, currentRoute, config); + } + + if (hasOptedOutOfGettingStarted()) { + return handleGettingStartedOptedOutScenario(currentRoute, kbnUrl); + } + + return showGettingStartedPage(kbnUrl, isOnGettingStartedPage); + }); +} + +// Start out with the chrome not being shown to prevent a flicker by +// hiding it later +uiChrome.setVisible(false); +uiRoutes.addSetupWork((Private, $injector) => { + const getIds = Private(IndexPatternsGetIdsProvider); + const kbnUrl = Private(KbnUrlProvider); + const config = $injector.get('config'); + const $route = $injector.get('$route'); + return gettingStartedGateCheck(getIds, kbnUrl, config, $route); +}); \ No newline at end of file diff --git a/src/core_plugins/getting_started/public/lib/constants.js b/src/core_plugins/getting_started/public/lib/constants.js new file mode 100644 index 0000000000000..2764ef2570208 --- /dev/null +++ b/src/core_plugins/getting_started/public/lib/constants.js @@ -0,0 +1,2 @@ +export const GETTING_STARTED_ROUTE = '/management/kibana/getting_started'; +export const CREATE_INDEX_PATTERN_ROUTE = '/management/kibana/index'; \ No newline at end of file diff --git a/src/core_plugins/getting_started/public/lib/register_management_section.js b/src/core_plugins/getting_started/public/lib/register_management_section.js new file mode 100644 index 0000000000000..f7759164ac16a --- /dev/null +++ b/src/core_plugins/getting_started/public/lib/register_management_section.js @@ -0,0 +1,8 @@ +import { management } from 'ui/management'; +import { GETTING_STARTED_ROUTE } from './constants'; + +management.getSection('kibana').register('getting_started', { + display: 'Getting Started', + order: 50, + url: `#${GETTING_STARTED_ROUTE}` +}); diff --git a/src/core_plugins/kibana/public/management/index.js b/src/core_plugins/kibana/public/management/index.js index 391d70fb9193b..91bd1dc7eede5 100644 --- a/src/core_plugins/kibana/public/management/index.js +++ b/src/core_plugins/kibana/public/management/index.js @@ -19,10 +19,6 @@ uiRoutes redirectTo: '/management' }); -require('ui/index_patterns/route_setup/load_default')({ - whenMissingRedirectTo: '/management/kibana/index' -}); - uiModules .get('apps/management') .directive('kbnManagementApp', function (Private, $location, timefilter, buildNum, buildSha) { diff --git a/src/ui/public/documentation_links/documentation_links.js b/src/ui/public/documentation_links/documentation_links.js index ec6f681b51152..cec22d65b9cbe 100644 --- a/src/ui/public/documentation_links/documentation_links.js +++ b/src/ui/public/documentation_links/documentation_links.js @@ -4,6 +4,18 @@ const urlVersion = metadata.branch; const baseUrl = 'https://www.elastic.co/'; export const documentationLinks = { + elasticsearch: { + docs: `${baseUrl}guide/en/elasticsearch/reference/current` + }, + beats: { + docs: `${baseUrl}guide/en/beats/libbeat/current` + }, + logstash: { + docs: `${baseUrl}guide/en/logstash/current` + }, + kibana: { + docs: `${baseUrl}guide/en/kibana/current` + }, filebeat: { installation: `${baseUrl}guide/en/beats/filebeat/${urlVersion}/filebeat-installation.html`, configuration: `${baseUrl}guide/en/beats/filebeat/${urlVersion}/filebeat-configuration.html`, @@ -26,4 +38,6 @@ export const documentationLinks = { date: { dateMath: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/common-options.html#date-math` }, + demoSite: 'http://demo.elastic.co', + gettingStarted: `${baseUrl}products/kibana/getting-started-link` }; diff --git a/src/ui/public/getting_started/constants.js b/src/ui/public/getting_started/constants.js new file mode 100644 index 0000000000000..a1b839d00ed67 --- /dev/null +++ b/src/ui/public/getting_started/constants.js @@ -0,0 +1,6 @@ +export const GETTING_STARTED_OPT_OUT_FLAG = 'kibana.isGettingStartedOptedOut'; + +export const GETTING_STARTED_REGISTRY_TYPES = { + TOP_MESSAGE: 'topMessage', + MANAGE_AND_MONITOR_MESSAGE: 'monitorAndManageMessage' +}; \ No newline at end of file diff --git a/src/ui/public/getting_started/opt_out_directive.js b/src/ui/public/getting_started/opt_out_directive.js new file mode 100644 index 0000000000000..2b5db91839300 --- /dev/null +++ b/src/ui/public/getting_started/opt_out_directive.js @@ -0,0 +1,14 @@ +import { uiModules } from 'ui/modules'; +import { optOutOfGettingStarted } from './opt_out_helpers'; + +const app = uiModules.get('kibana'); +app.directive('kbnGettingStartedOptOut', () => { + return { + restrict: 'A', + link: (scope, element) => { + element.on('click', () => { + optOutOfGettingStarted(); + }); + } + }; +}); diff --git a/src/ui/public/getting_started/opt_out_helpers.js b/src/ui/public/getting_started/opt_out_helpers.js new file mode 100644 index 0000000000000..9e07c34a42066 --- /dev/null +++ b/src/ui/public/getting_started/opt_out_helpers.js @@ -0,0 +1,16 @@ +import { GETTING_STARTED_OPT_OUT_FLAG } from './constants'; + +export function hasOptedOutOfGettingStarted() { + return Boolean(window.localStorage.getItem(GETTING_STARTED_OPT_OUT_FLAG)); +} + +export function optOutOfGettingStarted() { + window.localStorage.setItem(GETTING_STARTED_OPT_OUT_FLAG, true); +} + +/** + * This function is intended for unit testing + */ +export function undoOptOutOfGettingStarted() { + window.localStorage.removeItem(GETTING_STARTED_OPT_OUT_FLAG); +} \ No newline at end of file diff --git a/src/ui/public/getting_started/registry.js b/src/ui/public/getting_started/registry.js new file mode 100644 index 0000000000000..f7cb2ce174749 --- /dev/null +++ b/src/ui/public/getting_started/registry.js @@ -0,0 +1,18 @@ +import { uiRegistry } from 'ui/registry/_registry'; + +export const GettingStartedRegistryProvider = uiRegistry({ + name: 'gettingStartedTopMessages', + group: [ 'type' ] +}); + +/** + * Usage: + * + * import { GettingStartedRegistryProvider } from 'ui/getting_started/registry'; + * import { GETTING_STARTED_REGISTRY_TYPES } from 'ui/getting_started/constants'; + * + * GettingStartedRegistryProvider.register(($injector, Private, someOtherService, ...) => ({ + * type: GETTING_STARTED_REGISTRY_TYPES.TOP_MESSAGE, + * template: 'plain text | html markup | markup with directives' + * })); + */ diff --git a/src/ui/public/images/icon-dashboard.svg b/src/ui/public/images/icon-dashboard.svg new file mode 100644 index 0000000000000..d48ff0aa753dd --- /dev/null +++ b/src/ui/public/images/icon-dashboard.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/ui/public/images/icon-shield.svg b/src/ui/public/images/icon-shield.svg new file mode 100644 index 0000000000000..98a9fa0bdd82c --- /dev/null +++ b/src/ui/public/images/icon-shield.svg @@ -0,0 +1,14 @@ + + + + icon-security-bb-edited + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/src/ui/public/images/logo-beats-small.svg b/src/ui/public/images/logo-beats-small.svg new file mode 100644 index 0000000000000..b7666b0238dfb --- /dev/null +++ b/src/ui/public/images/logo-beats-small.svg @@ -0,0 +1,23 @@ + + + + +logo-menu + + + + + + + + diff --git a/src/ui/public/images/logo-kibana-small.svg b/src/ui/public/images/logo-kibana-small.svg new file mode 100644 index 0000000000000..094ff605c0fc5 --- /dev/null +++ b/src/ui/public/images/logo-kibana-small.svg @@ -0,0 +1,20 @@ + + + + +logo-menu + + + + + + + + diff --git a/src/ui/public/images/logo-logstash-small.svg b/src/ui/public/images/logo-logstash-small.svg new file mode 100644 index 0000000000000..5a4f62ad02195 --- /dev/null +++ b/src/ui/public/images/logo-logstash-small.svg @@ -0,0 +1,20 @@ + + + + +logo-menu + + + + + + + + diff --git a/src/ui/public/index_patterns/route_setup/load_default.js b/src/ui/public/index_patterns/route_setup/load_default.js deleted file mode 100644 index 715593144e845..0000000000000 --- a/src/ui/public/index_patterns/route_setup/load_default.js +++ /dev/null @@ -1,58 +0,0 @@ -import _ from 'lodash'; -import { Notifier } from 'ui/notify/notifier'; -import { NoDefaultIndexPattern } from 'ui/errors'; -import { IndexPatternsGetIdsProvider } from '../_get_ids'; -import uiRoutes from 'ui/routes'; -const notify = new Notifier({ - location: 'Index Patterns' -}); - -module.exports = function (opts) { - opts = opts || {}; - const whenMissingRedirectTo = opts.whenMissingRedirectTo || null; - let defaultRequiredToasts = null; - - uiRoutes - .addSetupWork(function loadDefaultIndexPattern(Private, Promise, $route, config) { - const getIds = Private(IndexPatternsGetIdsProvider); - const route = _.get($route, 'current.$$route'); - - return getIds() - .then(function (patterns) { - let defaultId = config.get('defaultIndex'); - let defined = !!defaultId; - const exists = _.contains(patterns, defaultId); - - if (defined && !exists) { - config.remove('defaultIndex'); - defaultId = defined = false; - } - - if (!defined && route.requireDefaultIndex) { - // If there is only one index pattern, set it as default - if (patterns.length === 1) { - defaultId = patterns[0]; - config.set('defaultIndex', defaultId); - } else { - throw new NoDefaultIndexPattern(); - } - } - }); - }) - .afterWork( - // success - null, - - // failure - function (err, kbnUrl) { - const hasDefault = !(err instanceof NoDefaultIndexPattern); - if (hasDefault || !whenMissingRedirectTo) throw err; // rethrow - - kbnUrl.change(whenMissingRedirectTo); - if (!defaultRequiredToasts) defaultRequiredToasts = []; - else defaultRequiredToasts.push(notify.error(err)); - } - ); - - -}; diff --git a/src/ui/public/notify/notifier.js b/src/ui/public/notify/notifier.js index 5395e44089111..0382c6cefeb2f 100644 --- a/src/ui/public/notify/notifier.js +++ b/src/ui/public/notify/notifier.js @@ -230,8 +230,8 @@ Notifier.config = { errorLifetime: 300000, warningLifetime: 10000, infoLifetime: 5000, - setInterval: window.setInterval, - clearInterval: window.clearInterval + setInterval: window.setInterval.bind(window), + clearInterval: window.clearInterval.bind(window) }; Notifier.applyConfig = function (config) { diff --git a/test/functional/apps/console/_console.js b/test/functional/apps/console/_console.js index 64d7190f57709..4eb302b1effa5 100644 --- a/test/functional/apps/console/_console.js +++ b/test/functional/apps/console/_console.js @@ -18,9 +18,9 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common', 'console']); describe('console app', function describeIndexTests() { - before(function () { + before(async function () { log.debug('navigateTo console'); - return PageObjects.common.navigateToApp('console'); + await PageObjects.common.navigateToApp('console'); }); it('should show the default request', function () { diff --git a/test/functional/apps/management/_getting_started.js b/test/functional/apps/management/_getting_started.js new file mode 100644 index 0000000000000..f8ffb6936d25c --- /dev/null +++ b/test/functional/apps/management/_getting_started.js @@ -0,0 +1,78 @@ +import expect from 'expect.js'; + +export default ({ getService, getPageObjects }) => { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const remote = getService('remote'); + const log = getService('log'); + + const PageObjects = getPageObjects(['common', 'gettingStarted']); + + describe('Getting Started page', () => { + describe('when no index patterns exist', () => { + beforeEach(async () => { + // delete .kibana index and then wait for Kibana to re-create it + await esArchiver.unload('logstash_functional'); + await esArchiver.load('empty_kibana'); + }); + + describe('when user has not opted out of Getting Started page', () => { + beforeEach(async () => { + // First, we navigate to *somewhere* in Kibana so the browser loads up Kibana. This allows us... + await PageObjects.common.navigateToUrl('discover', ''); + + // ... to remove the Getting Started page opt-out flag from local storage for the Kibana domain + await remote.deleteLocalStorageItem('kibana.isGettingStartedOptedOut'); + }); + + it('redirects to the Getting Started page', async () => { + await PageObjects.common.navigateToUrl('discover', ''); + await PageObjects.common.waitUntilUrlIncludes('getting_started'); + const isLoaded = await PageObjects.gettingStarted.doesContainerExist(); + expect(isLoaded).to.be(true); + }); + }); + + describe('when user has opted out of Getting Started page', () => { + beforeEach(async () => { + await PageObjects.gettingStarted.optOut(); + }); + + it('does not redirect to the Getting Started page', async () => { + await PageObjects.common.navigateToUrl('discover', ''); + const isLoaded = await PageObjects.gettingStarted.doesContainerExist(); + expect(isLoaded).to.be(false); + }); + }); + + }); + + describe('when index patterns exist', () => { + beforeEach(async () => { + log.debug('load kibana index with default index pattern'); + await esArchiver.load('discover'); + await kibanaServer.uiSettings.replace({ + 'dateFormat:tz':'UTC', + 'defaultIndex':'logstash-*' + }); + }); + + it('does not redirect to the Getting Started page', async () => { + await PageObjects.common.navigateToUrl('discover', ''); + const isLoaded = await PageObjects.gettingStarted.doesContainerExist(); + expect(isLoaded).to.be(false); + }); + + describe('when a user directly navigates to the Getting Started page', () => { + beforeEach(async () => { + await PageObjects.gettingStarted.navigateTo(); + }); + + it('the kibana chrome (which contains the global nav) is visible', async () => { + const isChromeVisible = await PageObjects.common.isChromeVisible(); + expect(isChromeVisible).to.be(true); + }); + }); + }); + }); +}; \ No newline at end of file diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js index afaea2d42cff7..a9c01555993ae 100644 --- a/test/functional/apps/management/index.js +++ b/test/functional/apps/management/index.js @@ -24,6 +24,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_scripted_fields')); loadTestFile(require.resolve('./_index_pattern_filter')); loadTestFile(require.resolve('./_scripted_fields_filter')); + loadTestFile(require.resolve('./_getting_started')); }); } diff --git a/test/functional/config.js b/test/functional/config.js index 4774bcd23cb23..6d6cc50dc720f 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -10,6 +10,7 @@ import { SettingsPageProvider, MonitoringPageProvider, PointSeriesPageProvider, + GettingStartedPageProvider } from './page_objects'; import { @@ -48,6 +49,7 @@ export default async function ({ readConfigFile }) { settings: SettingsPageProvider, monitoring: MonitoringPageProvider, pointSeries: PointSeriesPageProvider, + gettingStarted: GettingStartedPageProvider, }, services: { es: commonConfig.get('services.es'), diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js index 7d700c8fcd235..cbc8be50cc46b 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.js @@ -2,7 +2,7 @@ import { delay } from 'bluebird'; import getUrl from '../../../src/test_utils/get_url'; -export function CommonPageProvider({ getService, getPageObjects }) { +export function CommonPageProvider({ getService, getPageObjects, getPageObject }) { const log = getService('log'); const config = getService('config'); const remote = getService('remote'); @@ -87,6 +87,11 @@ export function CommonPageProvider({ getService, getPageObjects }) { if (currentUrl.includes('app/kibana')) { await testSubjects.find('kibanaChrome'); + const gettingStartedPage = getPageObject('gettingStarted'); + if (await gettingStartedPage.doesContainerExist()) { + await gettingStartedPage.optOut(); + throw new Error('Retrying after receiving Getting Started page'); + } } }) .then(async function () { diff --git a/test/functional/page_objects/getting_started_page.js b/test/functional/page_objects/getting_started_page.js new file mode 100644 index 0000000000000..aca06aac9d33c --- /dev/null +++ b/test/functional/page_objects/getting_started_page.js @@ -0,0 +1,31 @@ +export function GettingStartedPageProvider({ getService, getPageObjects }) { + + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + const PageObjects = getPageObjects(['common']); + + class GettingStartedPage { + async doesContainerExist() { + return await testSubjects.exists('gettingStartedContainer'); + } + + async optOut() { + log.debug('Clicking opt-out link'); + await testSubjects.click('lnkGettingStartedOptOut'); + await retry.try(async () => { + if (await this.doesContainerExist()) { + throw new Error('Still on getting started page'); + } + }); + } + + async navigateTo() { + log.debug('Navigating directly to Getting Started page'); + await PageObjects.common.navigateToUrl('settings', 'kibana/getting_started'); + } + } + + return new GettingStartedPage(); +} diff --git a/test/functional/page_objects/index.js b/test/functional/page_objects/index.js index ea811c354d8fd..c7cdb817c3bcb 100644 --- a/test/functional/page_objects/index.js +++ b/test/functional/page_objects/index.js @@ -9,3 +9,4 @@ export { VisualizePageProvider } from './visualize_page'; export { SettingsPageProvider } from './settings_page'; export { MonitoringPageProvider } from './monitoring_page'; export { PointSeriesPageProvider } from './point_series_page'; +export { GettingStartedPageProvider } from './getting_started_page';