From fbbbde625a5157b794c53298811f5fd30d74904d Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 27 May 2020 14:30:37 +0200 Subject: [PATCH 01/97] creating overview page and menu --- .../public/application/index.tsx | 25 +++++++------------ x-pack/plugins/observability/public/index.ts | 6 +++++ x-pack/plugins/observability/public/plugin.ts | 20 ++++++++++++--- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 21a9fabf445f1..a02e93c0204f3 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -5,24 +5,17 @@ */ import React from 'react'; import ReactDOM from 'react-dom'; -import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; +import { ConfigSchema } from '../'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; -import { Home } from '../pages/home'; -import { PluginContext } from '../context/plugin_context'; +import { PluginSetupDeps } from '../plugin'; -export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { - const i18nCore = core.i18n; - const isDarkMode = core.uiSettings.get('theme:darkMode'); - ReactDOM.render( - - - - - - - , - element - ); +export const renderApp = ( + core: CoreStart, + deps: PluginSetupDeps, + { element }: AppMountParameters, + config: ConfigSchema +) => { + ReactDOM.render(
Hello observability overview
, element); return () => { ReactDOM.unmountComponentAtNode(element); }; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index dd8bca90cff4f..901177fc23a86 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -7,6 +7,12 @@ import { PluginInitializerContext, PluginInitializer } from 'kibana/public'; import { Plugin, ClientSetup, ClientStart } from './plugin'; +export interface ConfigSchema { + ui: { + enabled: boolean; + }; +} + export const plugin: PluginInitializer = ( context: PluginInitializerContext ) => { diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index f2c88a7b1c056..8bf1640176673 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -3,10 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { ConfigSchema } from '.'; import { AppMountParameters, CoreSetup, DEFAULT_APP_CATEGORIES, + // CoreStart, Plugin as PluginClass, PluginInitializerContext, } from '../../../../src/core/public'; @@ -14,14 +16,24 @@ import { export type ClientSetup = void; export type ClientStart = void; +// eslint-disable-next-line +export interface PluginSetupDeps { +} + export class Plugin implements PluginClass { - constructor(context: PluginInitializerContext) {} + private readonly initializerContext: PluginInitializerContext; + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public setup(core: CoreSetup, plugins: PluginSetupDeps) { + const config = this.initializerContext.config.get(); + const pluginSetupDeps = plugins; - public setup(core: CoreSetup) { core.application.register({ id: 'observability-overview', title: 'Overview', - order: 8000, + order: 7999, appRoute: '/app/observability', category: DEFAULT_APP_CATEGORIES.observability, @@ -31,7 +43,7 @@ export class Plugin implements PluginClass { // Get start services const [coreStart] = await core.getStartServices(); - return renderApp(coreStart, params); + return renderApp(coreStart, pluginSetupDeps, params, config); }, }); } From 8a5ec483aa34f10bd51eae375aa7fba5c5815ec4 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 28 May 2020 15:30:48 +0200 Subject: [PATCH 02/97] styling the home page --- x-pack/plugins/observability/.prettierrc | 4 + .../public/application/index.tsx | 3 +- .../public/assets/observability-overview.png | Bin 0 -> 98273 bytes .../public/pages/Home/Section.tsx | 62 +++++ .../observability/public/pages/Home/index.tsx | 219 ++++++++++++++++++ 5 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/observability/.prettierrc create mode 100644 x-pack/plugins/observability/public/assets/observability-overview.png create mode 100644 x-pack/plugins/observability/public/pages/Home/Section.tsx create mode 100644 x-pack/plugins/observability/public/pages/Home/index.tsx diff --git a/x-pack/plugins/observability/.prettierrc b/x-pack/plugins/observability/.prettierrc new file mode 100644 index 0000000000000..650cb880f6f5a --- /dev/null +++ b/x-pack/plugins/observability/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": true +} diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index a02e93c0204f3..28bc05383232f 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -8,6 +8,7 @@ import ReactDOM from 'react-dom'; import { ConfigSchema } from '../'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { PluginSetupDeps } from '../plugin'; +import { Home } from '../pages/Home'; export const renderApp = ( core: CoreStart, @@ -15,7 +16,7 @@ export const renderApp = ( { element }: AppMountParameters, config: ConfigSchema ) => { - ReactDOM.render(
Hello observability overview
, element); + ReactDOM.render(, element); return () => { ReactDOM.unmountComponentAtNode(element); }; diff --git a/x-pack/plugins/observability/public/assets/observability-overview.png b/x-pack/plugins/observability/public/assets/observability-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..70be08af9745ad4d7ab427d9b3d4c1d2d2f1e75a GIT binary patch literal 98273 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�s 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

{^Fs1wC!ys~qb#haBPfuxcrj$rVVBk_t#x* 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 NE2St|D{dJ4{{R5buN43Q literal 0 HcmV?d00001 diff --git a/x-pack/plugins/observability/public/pages/Home/Section.tsx b/x-pack/plugins/observability/public/pages/Home/Section.tsx new file mode 100644 index 0000000000000..6af742fa9dfe5 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/Home/Section.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export interface ISection { + id: string; + title: string; + icon: string; + description: string; + href?: string; +} + +export const Section = ({ section }: { section: ISection }) => { + const { id, icon, title, description, href } = section; + + const sectionContent = ( + + + + + + +

+ {i18n.translate(`observability.section.${id}.title`, { + defaultMessage: title, + })} +

+ + + + {i18n.translate(`observability.section.${id}.description`, { + defaultMessage: description, + })} + + + + ); + + if (href) { + return ( + + + {sectionContent} + + + ); + } + return {sectionContent}; +}; diff --git a/x-pack/plugins/observability/public/pages/Home/index.tsx b/x-pack/plugins/observability/public/pages/Home/index.tsx new file mode 100644 index 0000000000000..27dac03d7728a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/Home/index.tsx @@ -0,0 +1,219 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiImage, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import styled from 'styled-components'; +import { ISection, Section } from './Section'; + +const appsSection: ISection[] = [ + { + id: 'logs', + title: 'Logs', + icon: 'logoLogging', + description: + 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + }, + { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: + 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + }, + { + id: 'metrics', + title: 'Metrics', + icon: 'logoMetrics', + description: + 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + }, + { + id: 'uptime', + title: 'Uptime', + icon: 'logoUptime', + description: + 'React to availability issues across your apps and services before they affect users.', + }, +]; + +const tryItOutItemsSection: ISection[] = [ + { + id: 'demo', + title: 'Demo Playground', + icon: 'play', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + href: 'https://demo.elastic.co/', + }, + { + id: 'sampleData', + title: 'Add sample data', + icon: 'documents', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + href: '/app/home#/tutorial_directory/sampleData', + }, +]; + +const FixedContainer = styled.div` + position: fixed; + width: 100%; + height: 100%; +`; + +const CentralizedContainer = styled.div` + width: 1200px; + margin: 0 auto; +`; + +const TitleContainer = styled(CentralizedContainer)` + height: 124px; + display: flex; + align-items: center; +`; + +const BodyContainer = styled.div` + background-color: #fff; + height: 100%; + box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), + 0 1px 5px -2px rgba(152, 162, 179, 0.3); + border: 1px solid #d3dae6; +`; + +export const Home = () => { + return ( + + + + + + + + +

+ {i18n.translate('observability.home.title', { + defaultMessage: 'Observability', + })} +

+
+
+
+
+ + + + + {/* title and description */} + + +

+ {i18n.translate('observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', + })} +

+
+ + + {i18n.translate('observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
+ + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( +
+ ))} + + + + + + + + + {/* Get started button */} + + + + + {i18n.translate('observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + + + + + + {/* Try it out */} + + + + +

+ {i18n.translate('observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
+
+
+
+ + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + +
+ + + ))} + + + + + + + ); +}; From 8078ff0bd0919fd92e3f8da4f4be9f7e24238c96 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 28 May 2020 17:27:53 +0200 Subject: [PATCH 03/97] adjusting breadcrumb --- .../public/application/index.tsx | 4 ++-- .../observability/public/pages/Home/index.tsx | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 28bc05383232f..b936551da91fe 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -8,7 +8,7 @@ import ReactDOM from 'react-dom'; import { ConfigSchema } from '../'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { PluginSetupDeps } from '../plugin'; -import { Home } from '../pages/Home'; +import { Home } from '../pages/home'; export const renderApp = ( core: CoreStart, @@ -16,7 +16,7 @@ export const renderApp = ( { element }: AppMountParameters, config: ConfigSchema ) => { - ReactDOM.render(, element); + ReactDOM.render(, element); return () => { ReactDOM.unmountComponentAtNode(element); }; diff --git a/x-pack/plugins/observability/public/pages/Home/index.tsx b/x-pack/plugins/observability/public/pages/Home/index.tsx index 27dac03d7728a..b9ac4de4011fc 100644 --- a/x-pack/plugins/observability/public/pages/Home/index.tsx +++ b/x-pack/plugins/observability/public/pages/Home/index.tsx @@ -18,9 +18,10 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; import { ISection, Section } from './Section'; +import { CoreStart } from '../../../../../../src/core/public'; const appsSection: ISection[] = [ { @@ -97,7 +98,25 @@ const BodyContainer = styled.div` border: 1px solid #d3dae6; `; -export const Home = () => { +interface Props { + core: CoreStart; +} + +export const Home = ({ core }: Props) => { + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, [core.chrome]); return ( From d5192d4dcd0164bac012112ce853a1287d382229 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 28 May 2020 17:33:38 +0200 Subject: [PATCH 04/97] renaming isnt working --- .../public/pages/home2/Section.tsx | 62 +++++ .../public/pages/home2/index.tsx | 238 ++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 x-pack/plugins/observability/public/pages/home2/Section.tsx create mode 100644 x-pack/plugins/observability/public/pages/home2/index.tsx diff --git a/x-pack/plugins/observability/public/pages/home2/Section.tsx b/x-pack/plugins/observability/public/pages/home2/Section.tsx new file mode 100644 index 0000000000000..6af742fa9dfe5 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/home2/Section.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export interface ISection { + id: string; + title: string; + icon: string; + description: string; + href?: string; +} + +export const Section = ({ section }: { section: ISection }) => { + const { id, icon, title, description, href } = section; + + const sectionContent = ( + + + + + + +

+ {i18n.translate(`observability.section.${id}.title`, { + defaultMessage: title, + })} +

+
+ + + {i18n.translate(`observability.section.${id}.description`, { + defaultMessage: description, + })} + +
+
+ ); + + if (href) { + return ( + + + {sectionContent} + + + ); + } + return {sectionContent}; +}; diff --git a/x-pack/plugins/observability/public/pages/home2/index.tsx b/x-pack/plugins/observability/public/pages/home2/index.tsx new file mode 100644 index 0000000000000..b9ac4de4011fc --- /dev/null +++ b/x-pack/plugins/observability/public/pages/home2/index.tsx @@ -0,0 +1,238 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiImage, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import styled from 'styled-components'; +import { ISection, Section } from './Section'; +import { CoreStart } from '../../../../../../src/core/public'; + +const appsSection: ISection[] = [ + { + id: 'logs', + title: 'Logs', + icon: 'logoLogging', + description: + 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + }, + { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: + 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + }, + { + id: 'metrics', + title: 'Metrics', + icon: 'logoMetrics', + description: + 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + }, + { + id: 'uptime', + title: 'Uptime', + icon: 'logoUptime', + description: + 'React to availability issues across your apps and services before they affect users.', + }, +]; + +const tryItOutItemsSection: ISection[] = [ + { + id: 'demo', + title: 'Demo Playground', + icon: 'play', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + href: 'https://demo.elastic.co/', + }, + { + id: 'sampleData', + title: 'Add sample data', + icon: 'documents', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + href: '/app/home#/tutorial_directory/sampleData', + }, +]; + +const FixedContainer = styled.div` + position: fixed; + width: 100%; + height: 100%; +`; + +const CentralizedContainer = styled.div` + width: 1200px; + margin: 0 auto; +`; + +const TitleContainer = styled(CentralizedContainer)` + height: 124px; + display: flex; + align-items: center; +`; + +const BodyContainer = styled.div` + background-color: #fff; + height: 100%; + box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), + 0 1px 5px -2px rgba(152, 162, 179, 0.3); + border: 1px solid #d3dae6; +`; + +interface Props { + core: CoreStart; +} + +export const Home = ({ core }: Props) => { + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, [core.chrome]); + return ( + + + + + + + + +

+ {i18n.translate('observability.home.title', { + defaultMessage: 'Observability', + })} +

+
+
+
+
+ + + + + {/* title and description */} + + +

+ {i18n.translate('observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', + })} +

+
+ + + {i18n.translate('observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
+ + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( +
+ ))} + + + + + + + + + {/* Get started button */} + + + + + {i18n.translate('observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + + + + + + {/* Try it out */} + + + + +

+ {i18n.translate('observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
+
+
+
+ + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + +
+ + + ))} + + + + + + + ); +}; From 1b3166b82ca4e97090bb1f09f8369ab6bd069ab3 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 28 May 2020 17:34:10 +0200 Subject: [PATCH 05/97] renaming isnt working --- .../public/pages/Home/Section.tsx | 62 ----- .../observability/public/pages/Home/index.tsx | 238 ------------------ 2 files changed, 300 deletions(-) delete mode 100644 x-pack/plugins/observability/public/pages/Home/Section.tsx delete mode 100644 x-pack/plugins/observability/public/pages/Home/index.tsx diff --git a/x-pack/plugins/observability/public/pages/Home/Section.tsx b/x-pack/plugins/observability/public/pages/Home/Section.tsx deleted file mode 100644 index 6af742fa9dfe5..0000000000000 --- a/x-pack/plugins/observability/public/pages/Home/Section.tsx +++ /dev/null @@ -1,62 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -export interface ISection { - id: string; - title: string; - icon: string; - description: string; - href?: string; -} - -export const Section = ({ section }: { section: ISection }) => { - const { id, icon, title, description, href } = section; - - const sectionContent = ( - - - - - - -

- {i18n.translate(`observability.section.${id}.title`, { - defaultMessage: title, - })} -

-
- - - {i18n.translate(`observability.section.${id}.description`, { - defaultMessage: description, - })} - -
-
- ); - - if (href) { - return ( - - - {sectionContent} - - - ); - } - return {sectionContent}; -}; diff --git a/x-pack/plugins/observability/public/pages/Home/index.tsx b/x-pack/plugins/observability/public/pages/Home/index.tsx deleted file mode 100644 index b9ac4de4011fc..0000000000000 --- a/x-pack/plugins/observability/public/pages/Home/index.tsx +++ /dev/null @@ -1,238 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiButton, - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIcon, - EuiImage, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import styled from 'styled-components'; -import { ISection, Section } from './Section'; -import { CoreStart } from '../../../../../../src/core/public'; - -const appsSection: ISection[] = [ - { - id: 'logs', - title: 'Logs', - icon: 'logoLogging', - description: - 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', - }, - { - id: 'apm', - title: 'APM', - icon: 'logoAPM', - description: - 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', - }, - { - id: 'metrics', - title: 'Metrics', - icon: 'logoMetrics', - description: - 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', - }, - { - id: 'uptime', - title: 'Uptime', - icon: 'logoUptime', - description: - 'React to availability issues across your apps and services before they affect users.', - }, -]; - -const tryItOutItemsSection: ISection[] = [ - { - id: 'demo', - title: 'Demo Playground', - icon: 'play', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', - href: 'https://demo.elastic.co/', - }, - { - id: 'sampleData', - title: 'Add sample data', - icon: 'documents', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', - href: '/app/home#/tutorial_directory/sampleData', - }, -]; - -const FixedContainer = styled.div` - position: fixed; - width: 100%; - height: 100%; -`; - -const CentralizedContainer = styled.div` - width: 1200px; - margin: 0 auto; -`; - -const TitleContainer = styled(CentralizedContainer)` - height: 124px; - display: flex; - align-items: center; -`; - -const BodyContainer = styled.div` - background-color: #fff; - height: 100%; - box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), - 0 1px 5px -2px rgba(152, 162, 179, 0.3); - border: 1px solid #d3dae6; -`; - -interface Props { - core: CoreStart; -} - -export const Home = ({ core }: Props) => { - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, [core.chrome]); - return ( - - - - - - - - -

- {i18n.translate('observability.home.title', { - defaultMessage: 'Observability', - })} -

-
-
-
-
- - - - - {/* title and description */} - - -

- {i18n.translate('observability.home.sectionTitle', { - defaultMessage: 'Observability built on the Elastic Stack', - })} -

-
- - - {i18n.translate('observability.home.sectionsubtitle', { - defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', - })} - -
- - {/* Apps sections */} - - - - - - {appsSection.map((app) => ( -
- ))} - - - - - - - - - {/* Get started button */} - - - - - {i18n.translate('observability.home.getStatedButton', { - defaultMessage: 'Get started', - })} - - - - - - - - {/* Try it out */} - - - - -

- {i18n.translate('observability.home.tryItOut', { - defaultMessage: 'Try it out', - })} -

-
-
-
-
- - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - -
- - - ))} - - - - - - - ); -}; From 1a8d95c764845969f8377e30a12be360ab0ff3b7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 28 May 2020 17:41:11 +0200 Subject: [PATCH 06/97] renaming isnt working --- .../observability/public/pages/home/index.tsx | 335 ++++++++++-------- .../{home2/Section.tsx => home/section.tsx} | 0 .../public/pages/home2/index.tsx | 238 ------------- 3 files changed, 184 insertions(+), 389 deletions(-) rename x-pack/plugins/observability/public/pages/{home2/Section.tsx => home/section.tsx} (100%) delete mode 100644 x-pack/plugins/observability/public/pages/home2/index.tsx diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index b9b567bef4ab4..b9ac4de4011fc 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -6,13 +6,13 @@ import { EuiButton, - EuiCard, EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiIcon, EuiImage, + EuiPanel, EuiSpacer, EuiText, EuiTitle, @@ -20,186 +20,219 @@ import { import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import styled from 'styled-components'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { appsSection, tryItOutItemsSection } from './section'; +import { ISection, Section } from './Section'; +import { CoreStart } from '../../../../../../src/core/public'; -const Container = styled.div` - min-height: calc(100vh - 48px); - background: ${(props) => props.theme.eui.euiColorEmptyShade}; -`; +const appsSection: ISection[] = [ + { + id: 'logs', + title: 'Logs', + icon: 'logoLogging', + description: + 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + }, + { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: + 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + }, + { + id: 'metrics', + title: 'Metrics', + icon: 'logoMetrics', + description: + 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + }, + { + id: 'uptime', + title: 'Uptime', + icon: 'logoUptime', + description: + 'React to availability issues across your apps and services before they affect users.', + }, +]; -const Title = styled.div` - background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; - border-bottom: ${(props) => props.theme.eui.euiBorderThin}; -`; +const tryItOutItemsSection: ISection[] = [ + { + id: 'demo', + title: 'Demo Playground', + icon: 'play', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + href: 'https://demo.elastic.co/', + }, + { + id: 'sampleData', + title: 'Add sample data', + icon: 'documents', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + href: '/app/home#/tutorial_directory/sampleData', + }, +]; -const Page = styled.div` +const FixedContainer = styled.div` + position: fixed; width: 100%; - max-width: 1200px; + height: 100%; +`; + +const CentralizedContainer = styled.div` + width: 1200px; margin: 0 auto; - overflow: hidden; -} `; -const EuiCardWithoutPadding = styled(EuiCard)` - padding: 0; +const TitleContainer = styled(CentralizedContainer)` + height: 124px; + display: flex; + align-items: center; +`; + +const BodyContainer = styled.div` + background-color: #fff; + height: 100%; + box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), + 0 1px 5px -2px rgba(152, 162, 179, 0.3); + border: 1px solid #d3dae6; `; -export const Home = () => { - const { core } = usePluginContext(); +interface Props { + core: CoreStart; +} +export const Home = ({ core }: Props) => { useEffect(() => { core.chrome.setBreadcrumbs([ { - text: i18n.translate('xpack.observability.home.breadcrumb.observability', { + text: i18n.translate('observability.home.breadcrumb.observability', { defaultMessage: 'Observability', }), }, { - text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { + text: i18n.translate('observability.home.breadcrumb.gettingStarted', { defaultMessage: 'Getting started', }), }, ]); - }, [core]); - + }, [core.chrome]); return ( - - - <Page> - <EuiSpacer size="xxl" /> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiIcon type="logoObservability" size="xxl" /> - </EuiFlexItem> - <EuiFlexItem> - <EuiTitle size="m"> - <h1> - {i18n.translate('xpack.observability.home.title', { - defaultMessage: 'Observability', - })} - </h1> - </EuiTitle> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="xxl" /> - </Page> - - - - - {/* title and description */} - - -

- {i18n.translate('xpack.observability.home.sectionTitle', { - defaultMessage: 'Observability built on the Elastic Stack', - })} -

-
- - - {i18n.translate('xpack.observability.home.sectionsubtitle', { - defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', - })} - + + + + + - - {/* Apps sections */} - - - - - {appsSection.map((app) => ( - - } - title={ - -

{app.title}

-
- } - description={app.description} - /> -
- ))} -
-
- - - -
+ +

+ {i18n.translate('observability.home.title', { + defaultMessage: 'Observability', + })} +

+
- - {/* Get started button */} - - - - - {i18n.translate('xpack.observability.home.getStatedButton', { - defaultMessage: 'Get started', + +
+ + + + + {/* title and description */} + + +

+ {i18n.translate('observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', })} - - - - +

+
+ + + {i18n.translate('observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
- + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( +
+ ))} + + + + + + + - {/* Try it out */} - - - - -

- {i18n.translate('xpack.observability.home.tryItOut', { - defaultMessage: 'Try it out', + {/* Get started button */} + + + + + {i18n.translate('observability.home.getStatedButton', { + defaultMessage: 'Get started', })} -

-
-
-
-
+ + + + - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - } - title={ - -

{item.title}

-
- } - description={item.description} - target={item.target} - href={item.href} - /> + + + {/* Try it out */} + + + + +

+ {i18n.translate('observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
- ))} -
- -
-
- - + +
+ + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + +
+ + + ))} + + + + + + ); }; diff --git a/x-pack/plugins/observability/public/pages/home2/Section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/home2/Section.tsx rename to x-pack/plugins/observability/public/pages/home/section.tsx diff --git a/x-pack/plugins/observability/public/pages/home2/index.tsx b/x-pack/plugins/observability/public/pages/home2/index.tsx deleted file mode 100644 index b9ac4de4011fc..0000000000000 --- a/x-pack/plugins/observability/public/pages/home2/index.tsx +++ /dev/null @@ -1,238 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiButton, - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIcon, - EuiImage, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import styled from 'styled-components'; -import { ISection, Section } from './Section'; -import { CoreStart } from '../../../../../../src/core/public'; - -const appsSection: ISection[] = [ - { - id: 'logs', - title: 'Logs', - icon: 'logoLogging', - description: - 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', - }, - { - id: 'apm', - title: 'APM', - icon: 'logoAPM', - description: - 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', - }, - { - id: 'metrics', - title: 'Metrics', - icon: 'logoMetrics', - description: - 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', - }, - { - id: 'uptime', - title: 'Uptime', - icon: 'logoUptime', - description: - 'React to availability issues across your apps and services before they affect users.', - }, -]; - -const tryItOutItemsSection: ISection[] = [ - { - id: 'demo', - title: 'Demo Playground', - icon: 'play', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', - href: 'https://demo.elastic.co/', - }, - { - id: 'sampleData', - title: 'Add sample data', - icon: 'documents', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', - href: '/app/home#/tutorial_directory/sampleData', - }, -]; - -const FixedContainer = styled.div` - position: fixed; - width: 100%; - height: 100%; -`; - -const CentralizedContainer = styled.div` - width: 1200px; - margin: 0 auto; -`; - -const TitleContainer = styled(CentralizedContainer)` - height: 124px; - display: flex; - align-items: center; -`; - -const BodyContainer = styled.div` - background-color: #fff; - height: 100%; - box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), - 0 1px 5px -2px rgba(152, 162, 179, 0.3); - border: 1px solid #d3dae6; -`; - -interface Props { - core: CoreStart; -} - -export const Home = ({ core }: Props) => { - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, [core.chrome]); - return ( - - - - - - - - -

- {i18n.translate('observability.home.title', { - defaultMessage: 'Observability', - })} -

-
-
-
-
- - - - - {/* title and description */} - - -

- {i18n.translate('observability.home.sectionTitle', { - defaultMessage: 'Observability built on the Elastic Stack', - })} -

-
- - - {i18n.translate('observability.home.sectionsubtitle', { - defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', - })} - -
- - {/* Apps sections */} - - - - - - {appsSection.map((app) => ( -
- ))} - - - - - - - - - {/* Get started button */} - - - - - {i18n.translate('observability.home.getStatedButton', { - defaultMessage: 'Get started', - })} - - - - - - - - {/* Try it out */} - - - - -

- {i18n.translate('observability.home.tryItOut', { - defaultMessage: 'Try it out', - })} -

-
-
-
-
- - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - -
- - - ))} - - - - - - - ); -}; From 6504be02e1d1da1de57ca09a2e4ab30c9697a70f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 29 May 2020 09:20:35 +0200 Subject: [PATCH 07/97] fixing import --- x-pack/plugins/observability/public/pages/home/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index b9ac4de4011fc..f3e89fe505edf 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -20,7 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import styled from 'styled-components'; -import { ISection, Section } from './Section'; +import { ISection, Section } from './section'; import { CoreStart } from '../../../../../../src/core/public'; const appsSection: ISection[] = [ From 0ee34a99d2c26633651a98751919adbffa9b6fb4 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 29 May 2020 13:00:48 +0200 Subject: [PATCH 08/97] fixing scroll when resize window --- .../observability/public/pages/home/index.tsx | 241 +++++++++--------- 1 file changed, 127 insertions(+), 114 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index f3e89fe505edf..8f327ea99021b 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -76,7 +76,17 @@ const tryItOutItemsSection: ISection[] = [ const FixedContainer = styled.div` position: fixed; width: 100%; + top: 49px; + bottom: 0; + display: flex; + flex-direction: column; +`; + +const OverflowContainer = styled.div` height: 100%; + overflow-y: auto; + overflow-x: hidden; + background-color: #fff; `; const CentralizedContainer = styled.div` @@ -84,18 +94,15 @@ const CentralizedContainer = styled.div` margin: 0 auto; `; -const TitleContainer = styled(CentralizedContainer)` +const TitleContainer = styled.div` height: 124px; display: flex; align-items: center; + background-color: #fafbfd; `; const BodyContainer = styled.div` - background-color: #fff; - height: 100%; - box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), - 0 1px 5px -2px rgba(152, 162, 179, 0.3); - border: 1px solid #d3dae6; + border-top: 1px solid #d3dae6; `; interface Props { @@ -119,120 +126,126 @@ export const Home = ({ core }: Props) => { }, [core.chrome]); return ( - - - - - - - -

- {i18n.translate('observability.home.title', { - defaultMessage: 'Observability', - })} -

-
-
-
-
- - - - - {/* title and description */} - - -

- {i18n.translate('observability.home.sectionTitle', { - defaultMessage: 'Observability built on the Elastic Stack', - })} -

-
- - - {i18n.translate('observability.home.sectionsubtitle', { - defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', - })} - -
- - {/* Apps sections */} - - - - - - {appsSection.map((app) => ( -
- ))} - - - - - - - - - {/* Get started button */} - - - - - {i18n.translate('observability.home.getStatedButton', { - defaultMessage: 'Get started', + + + + + + + + + +

+ {i18n.translate('observability.home.title', { + defaultMessage: 'Observability', + })} +

+
+
+
+
+
+ + + + + {/* title and description */} + + +

+ {i18n.translate('observability.home.sectionTitle', { + defaultMessage: + 'Observability built on the Elastic Stack', })} - - - - +

+
+ + + {i18n.translate('observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
- + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( +
+ ))} + + + + + + + - {/* Try it out */} - - - - -

- {i18n.translate('observability.home.tryItOut', { - defaultMessage: 'Try it out', + {/* Get started button */} + + + + + {i18n.translate('observability.home.getStatedButton', { + defaultMessage: 'Get started', })} -

-
-
-
-
+ + + + - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - -
- + + + {/* Try it out */} + + + + +

+ {i18n.translate('observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
- ))} -
-
- - - + + + + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + +
+ + + ))} + + + + + + + ); }; From ac9e9037e3e717e73dfce6a029aacc66ab7ccab1 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 29 May 2020 13:14:42 +0200 Subject: [PATCH 09/97] fixing eslint errors --- .../public/application/index.tsx | 9 +---- .../public/components/action_menu.tsx | 8 +++-- .../public/hooks/use_track_metric.tsx | 24 ++++++++++--- x-pack/plugins/observability/public/index.ts | 6 ---- x-pack/plugins/observability/public/plugin.ts | 18 ++-------- .../public/typings/eui_draggable/index.ts | 13 +++++-- .../public/typings/eui_styled_components.tsx | 15 ++++++-- x-pack/plugins/observability/server/index.ts | 5 ++- .../lib/annotations/bootstrap_annotations.ts | 12 +++++-- .../annotations/create_annotations_client.ts | 4 ++- .../annotations/register_annotation_apis.ts | 36 +++++++++++++------ x-pack/plugins/observability/server/plugin.ts | 5 ++- .../server/utils/create_or_update_index.ts | 3 +- .../plugins/observability/typings/common.ts | 4 ++- 14 files changed, 102 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index b936551da91fe..51c083d46f061 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -5,17 +5,10 @@ */ import React from 'react'; import ReactDOM from 'react-dom'; -import { ConfigSchema } from '../'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; -import { PluginSetupDeps } from '../plugin'; import { Home } from '../pages/home'; -export const renderApp = ( - core: CoreStart, - deps: PluginSetupDeps, - { element }: AppMountParameters, - config: ConfigSchema -) => { +export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { ReactDOM.render(, element); return () => { ReactDOM.unmountComponentAtNode(element); diff --git a/x-pack/plugins/observability/public/components/action_menu.tsx b/x-pack/plugins/observability/public/components/action_menu.tsx index ea79f4d08d701..9951dc69df15a 100644 --- a/x-pack/plugins/observability/public/components/action_menu.tsx +++ b/x-pack/plugins/observability/public/components/action_menu.tsx @@ -58,6 +58,10 @@ export const SectionLink: React.FC = (props) => ( ); -export const ActionMenuDivider: React.FC<{}> = (props) => ; +export const ActionMenuDivider: React.FC<{}> = (props) => ( + +); -export const ActionMenu: React.FC = (props) => ; +export const ActionMenu: React.FC = (props) => ( + +); diff --git a/x-pack/plugins/observability/public/hooks/use_track_metric.tsx b/x-pack/plugins/observability/public/hooks/use_track_metric.tsx index b146a80e89ea8..61f0eef843e7c 100644 --- a/x-pack/plugins/observability/public/hooks/use_track_metric.tsx +++ b/x-pack/plugins/observability/public/hooks/use_track_metric.tsx @@ -38,9 +38,14 @@ export { METRIC_TYPE }; export function useUiTracker({ app: defaultApp, }: { app?: ObservabilityApp } = {}) { - const reportUiStats = useKibana().services?.usageCollection?.reportUiStats; + const reportUiStats = useKibana().services?.usageCollection + ?.reportUiStats; const trackEvent = useMemo(() => { - return ({ app = defaultApp, metric, metricType = METRIC_TYPE.COUNT }: TrackMetricOptions) => { + return ({ + app = defaultApp, + metric, + metricType = METRIC_TYPE.COUNT, + }: TrackMetricOptions) => { if (reportUiStats) { reportUiStats(app as string, metricType, metric); } @@ -50,10 +55,16 @@ export function useUiTracker({ } export function useTrackMetric( - { app, metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions, + { + app, + metric, + metricType = METRIC_TYPE.COUNT, + delay = 0, + }: TrackMetricOptions, effectDependencies: EffectDeps = [] ) { - const reportUiStats = useKibana().services?.usageCollection?.reportUiStats; + const reportUiStats = useKibana().services?.usageCollection + ?.reportUiStats; useEffect(() => { if (!reportUiStats) { @@ -88,5 +99,8 @@ export function useTrackPageview( { path, ...rest }: TrackPageviewProps, effectDependencies: EffectDeps = [] ) { - useTrackMetric({ ...rest, metric: `pageview__${path}` }, effectDependencies); + useTrackMetric( + { ...rest, metric: `pageview__${path}` }, + effectDependencies + ); } diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 901177fc23a86..dd8bca90cff4f 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -7,12 +7,6 @@ import { PluginInitializerContext, PluginInitializer } from 'kibana/public'; import { Plugin, ClientSetup, ClientStart } from './plugin'; -export interface ConfigSchema { - ui: { - enabled: boolean; - }; -} - export const plugin: PluginInitializer = ( context: PluginInitializerContext ) => { diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 8bf1640176673..4888fe6b5fd43 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -3,12 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ConfigSchema } from '.'; import { AppMountParameters, CoreSetup, DEFAULT_APP_CATEGORIES, - // CoreStart, Plugin as PluginClass, PluginInitializerContext, } from '../../../../src/core/public'; @@ -16,20 +14,10 @@ import { export type ClientSetup = void; export type ClientStart = void; -// eslint-disable-next-line -export interface PluginSetupDeps { -} - export class Plugin implements PluginClass { - private readonly initializerContext: PluginInitializerContext; - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } - - public setup(core: CoreSetup, plugins: PluginSetupDeps) { - const config = this.initializerContext.config.get(); - const pluginSetupDeps = plugins; + constructor(context: PluginInitializerContext) {} + public setup(core: CoreSetup) { core.application.register({ id: 'observability-overview', title: 'Overview', @@ -43,7 +31,7 @@ export class Plugin implements PluginClass { // Get start services const [coreStart] = await core.getStartServices(); - return renderApp(coreStart, pluginSetupDeps, params, config); + return renderApp(coreStart, params); }, }); } diff --git a/x-pack/plugins/observability/public/typings/eui_draggable/index.ts b/x-pack/plugins/observability/public/typings/eui_draggable/index.ts index 322966b3c982e..286b27182b50d 100644 --- a/x-pack/plugins/observability/public/typings/eui_draggable/index.ts +++ b/x-pack/plugins/observability/public/typings/eui_draggable/index.ts @@ -7,11 +7,18 @@ import React from 'react'; import { EuiDraggable, EuiDragDropContext } from '@elastic/eui'; -type PropsOf = T extends React.ComponentType ? ComponentProps : never; -type FirstArgumentOf = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any +type PropsOf = T extends React.ComponentType + ? ComponentProps + : never; +type FirstArgumentOf = Func extends ( + arg1: infer FirstArgument, + ...rest: any[] +) => any ? FirstArgument : never; export type DragHandleProps = FirstArgumentOf< Exclude['children'], React.ReactElement> >['dragHandleProps']; -export type DropResult = FirstArgumentOf['onDragEnd']>; +export type DropResult = FirstArgumentOf< + FirstArgumentOf['onDragEnd'] +>; diff --git a/x-pack/plugins/observability/public/typings/eui_styled_components.tsx b/x-pack/plugins/observability/public/typings/eui_styled_components.tsx index aab16f9d79c4b..020b3a7294b02 100644 --- a/x-pack/plugins/observability/public/typings/eui_styled_components.tsx +++ b/x-pack/plugins/observability/public/typings/eui_styled_components.tsx @@ -6,7 +6,11 @@ import React from 'react'; import * as styledComponents from 'styled-components'; -import { ThemedStyledComponentsModule, ThemeProvider, ThemeProviderProps } from 'styled-components'; +import { + ThemedStyledComponentsModule, + ThemeProvider, + ThemeProviderProps, +} from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; @@ -42,4 +46,11 @@ const { withTheme, } = (styledComponents as unknown) as ThemedStyledComponentsModule; -export { css, euiStyled, EuiThemeProvider, createGlobalStyle, keyframes, withTheme }; +export { + css, + euiStyled, + EuiThemeProvider, + createGlobalStyle, + keyframes, + withTheme, +}; diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 78550b781b411..8fe8d3b2a3844 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -7,7 +7,10 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from 'src/core/server'; import { ObservabilityPlugin, ObservabilityPluginSetup } from './plugin'; -import { createOrUpdateIndex, MappingsDefinition } from './utils/create_or_update_index'; +import { + createOrUpdateIndex, + MappingsDefinition, +} from './utils/create_or_update_index'; import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations'; export const config = { diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index f57e1a774a8e2..f39b9440800ac 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -23,7 +23,9 @@ export type ScopedAnnotationsClientFactory = PromiseReturnType< typeof bootstrapAnnotations >['getScopedAnnotationsClient']; -export type ScopedAnnotationsClient = ReturnType; +export type ScopedAnnotationsClient = ReturnType< + ScopedAnnotationsClientFactory +>; export type AnnotationsAPI = PromiseReturnType; export async function bootstrapAnnotations({ index, core, context }: Params) { @@ -36,10 +38,14 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { }); return { - getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { + getScopedAnnotationsClient: ( + requestContext: RequestHandlerContext, + request: KibanaRequest + ) => { return createAnnotationsClient({ index, - apiCaller: requestContext.core.elasticsearch.legacy.client.callAsCurrentUser, + apiCaller: + requestContext.core.elasticsearch.dataClient.callAsCurrentUser, logger, license: requestContext.licensing?.license, }); diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 71b1a42b2000d..35107278cd60e 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -57,7 +57,9 @@ export function createAnnotationsClient(params: { function ensureGoldLicense any>(fn: T): T { return ((...args) => { if (!license?.hasAtLeast('gold')) { - throw Boom.forbidden('Annotations require at least a gold license or a trial license.'); + throw Boom.forbidden( + 'Annotations require at least a gold license or a trial license.' + ); } return fn(...args); }) as T; diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 5d0fdc65117bf..31c0bbdf3122d 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -29,7 +29,10 @@ export function registerAnnotationAPIs({ }) { function wrapRouteHandler>( types: TType, - handler: (params: { data: t.TypeOf; client: ScopedAnnotationsClient }) => Promise + handler: (params: { + data: t.TypeOf; + client: ScopedAnnotationsClient; + }) => Promise ): RequestHandler { return async (...args: Parameters) => { const [context, request, response] = args; @@ -72,7 +75,9 @@ export function registerAnnotationAPIs({ return response.custom({ statusCode: err.output?.statusCode ?? 500, body: { - message: err.output?.payload?.message ?? 'An internal server error occured', + message: + err.output?.payload?.message ?? + 'An internal server error occured', }, }); } @@ -88,9 +93,12 @@ export function registerAnnotationAPIs({ body: unknowns, }, }, - wrapRouteHandler(t.type({ body: createAnnotationRt }), ({ data, client }) => { - return client.create(data.body); - }) + wrapRouteHandler( + t.type({ body: createAnnotationRt }), + ({ data, client }) => { + return client.create(data.body); + } + ) ); router.delete( @@ -100,9 +108,12 @@ export function registerAnnotationAPIs({ params: unknowns, }, }, - wrapRouteHandler(t.type({ params: deleteAnnotationRt }), ({ data, client }) => { - return client.delete(data.params); - }) + wrapRouteHandler( + t.type({ params: deleteAnnotationRt }), + ({ data, client }) => { + return client.delete(data.params); + } + ) ); router.get( @@ -112,8 +123,11 @@ export function registerAnnotationAPIs({ params: unknowns, }, }, - wrapRouteHandler(t.type({ params: getAnnotationByIdRt }), ({ data, client }) => { - return client.getById(data.params); - }) + wrapRouteHandler( + t.type({ params: getAnnotationByIdRt }), + ({ data, client }) => { + return client.getById(data.params); + } + ) ); } diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index ee328c4deb082..d64e21d9e356b 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -26,7 +26,10 @@ export class ObservabilityPlugin implements Plugin { this.initContext = initContext; } - public async setup(core: CoreSetup, plugins: {}): Promise { + public async setup( + core: CoreSetup, + plugins: {} + ): Promise { const config$ = this.initContext.config.create(); const config = await config$.pipe(take(1)).toPromise(); diff --git a/x-pack/plugins/observability/server/utils/create_or_update_index.ts b/x-pack/plugins/observability/server/utils/create_or_update_index.ts index aa12b2227b7d2..f35cfc30de592 100644 --- a/x-pack/plugins/observability/server/utils/create_or_update_index.ts +++ b/x-pack/plugins/observability/server/utils/create_or_update_index.ts @@ -57,7 +57,8 @@ export async function createOrUpdateIndex({ }); if (!result.acknowledged) { - const resultError = result && result.error && JSON.stringify(result.error); + const resultError = + result && result.error && JSON.stringify(result.error); throw new Error(resultError); } }, diff --git a/x-pack/plugins/observability/typings/common.ts b/x-pack/plugins/observability/typings/common.ts index b4a90934a9f49..69593ccd50bc5 100644 --- a/x-pack/plugins/observability/typings/common.ts +++ b/x-pack/plugins/observability/typings/common.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export type PromiseReturnType = Func extends (...args: any[]) => Promise +export type PromiseReturnType = Func extends ( + ...args: any[] +) => Promise ? Value : Func; From 6b54ca71a1d76970085a7b1bd4ad5d1030cee093 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 29 May 2020 14:23:40 +0200 Subject: [PATCH 10/97] prepending links --- .../public/application/index.tsx | 8 ++- .../observability/public/pages/home/index.tsx | 51 +++++++++++-------- .../public/pages/home/section.tsx | 7 ++- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 51c083d46f061..805a0e1a07a9d 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -7,9 +7,15 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { Home } from '../pages/home'; +import { PluginContext } from '../context/plugin_context'; export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { - ReactDOM.render(, element); + ReactDOM.render( + + + , + element + ); return () => { ReactDOM.unmountComponentAtNode(element); }; diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 8f327ea99021b..7cb08550569fa 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { ISection, Section } from './section'; -import { CoreStart } from '../../../../../../src/core/public'; +import { usePluginContext } from '../../hooks/use_plugin_context'; const appsSection: ISection[] = [ { @@ -105,25 +105,28 @@ const BodyContainer = styled.div` border-top: 1px solid #d3dae6; `; -interface Props { - core: CoreStart; -} - -export const Home = ({ core }: Props) => { - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, [core.chrome]); +export const Home = () => { + const { core } = usePluginContext(); + + useEffect( + () => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + return ( @@ -183,7 +186,9 @@ export const Home = ({ core }: Props) => { @@ -198,7 +203,9 @@ export const Home = ({ core }: Props) => { iconType="sortRight" iconSide="right" style={{ width: 175 }} - href="/app/home#/tutorial_directory/logging" + href={core.http.basePath.prepend( + '/app/home#/tutorial_directory/logging' + )} > {i18n.translate('observability.home.getStatedButton', { defaultMessage: 'Get started', diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx index 6af742fa9dfe5..049bf71ba7fb7 100644 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ b/x-pack/plugins/observability/public/pages/home/section.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { usePluginContext } from '../../hooks/use_plugin_context'; export interface ISection { id: string; @@ -24,6 +25,7 @@ export interface ISection { } export const Section = ({ section }: { section: ISection }) => { + const { core } = usePluginContext(); const { id, icon, title, description, href } = section; const sectionContent = ( @@ -52,7 +54,10 @@ export const Section = ({ section }: { section: ISection }) => { if (href) { return ( - + {sectionContent} From ef3049018f1ce92fa02e26a4f80bb5667422c382 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 29 May 2020 14:31:42 +0200 Subject: [PATCH 11/97] adding target option --- x-pack/plugins/observability/public/pages/home/index.tsx | 1 + x-pack/plugins/observability/public/pages/home/section.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 7cb08550569fa..8df46f5b9c511 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -62,6 +62,7 @@ const tryItOutItemsSection: ISection[] = [ description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', href: 'https://demo.elastic.co/', + target: '_blank', }, { id: 'sampleData', diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx index 049bf71ba7fb7..cd9c6adee18f0 100644 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ b/x-pack/plugins/observability/public/pages/home/section.tsx @@ -22,6 +22,7 @@ export interface ISection { icon: string; description: string; href?: string; + target?: '_blank'; } export const Section = ({ section }: { section: ISection }) => { @@ -55,6 +56,7 @@ export const Section = ({ section }: { section: ISection }) => { return ( From 3f88d6d6ee666e5ed15ba7b1bd979372dfbc3f31 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 1 Jun 2020 14:06:42 +0200 Subject: [PATCH 12/97] refactoring --- x-pack/plugins/observability/.prettierrc | 4 - .../observability/public/pages/home/index.tsx | 268 ++++++++---------- .../public/pages/home/section.tsx | 33 ++- 3 files changed, 148 insertions(+), 157 deletions(-) delete mode 100644 x-pack/plugins/observability/.prettierrc diff --git a/x-pack/plugins/observability/.prettierrc b/x-pack/plugins/observability/.prettierrc deleted file mode 100644 index 650cb880f6f5a..0000000000000 --- a/x-pack/plugins/observability/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": true, - "semi": true -} diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 8df46f5b9c511..cd2455a4e0c72 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -59,8 +59,7 @@ const tryItOutItemsSection: ISection[] = [ id: 'demo', title: 'Demo Playground', icon: 'play', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + description: '', href: 'https://demo.elastic.co/', target: '_blank', }, @@ -68,42 +67,27 @@ const tryItOutItemsSection: ISection[] = [ id: 'sampleData', title: 'Add sample data', icon: 'documents', - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit amet magna at neque dictum.', + description: '', href: '/app/home#/tutorial_directory/sampleData', }, ]; -const FixedContainer = styled.div` - position: fixed; - width: 100%; - top: 49px; - bottom: 0; - display: flex; - flex-direction: column; -`; - -const OverflowContainer = styled.div` - height: 100%; - overflow-y: auto; - overflow-x: hidden; - background-color: #fff; +const Container = styled.div` + min-height: calc(100vh - 48px); + background: #fff; `; -const CentralizedContainer = styled.div` - width: 1200px; - margin: 0 auto; -`; - -const TitleContainer = styled.div` - height: 124px; - display: flex; - align-items: center; +const Title = styled.div` background-color: #fafbfd; + border-bottom: 1px solid #d3dae6; `; -const BodyContainer = styled.div` - border-top: 1px solid #d3dae6; +const Page = styled.div` + width: 100%; + max-width: 1200px; + margin: 0 auto; + overflow-y: hidden; +} `; export const Home = () => { @@ -129,131 +113,127 @@ export const Home = () => { ); return ( - - - - + + + <Page> + <EuiSpacer size="xxl" /> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiIcon type="logoObservability" size="xxl" /> + </EuiFlexItem> + <EuiFlexItem> + <EuiTitle size="m"> + <h1> + {i18n.translate('observability.home.title', { + defaultMessage: 'Observability', + })} + </h1> + </EuiTitle> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xxl" /> + </Page> + + + + + {/* title and description */} + + +

+ {i18n.translate('observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', + })} +

+
+ + + {i18n.translate('observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
+ + {/* Apps sections */} + + - - - - -

- {i18n.translate('observability.home.title', { - defaultMessage: 'Observability', - })} -

-
-
-
-
-
- - - - - {/* title and description */} - - -

- {i18n.translate('observability.home.sectionTitle', { - defaultMessage: - 'Observability built on the Elastic Stack', - })} -

-
- - - {i18n.translate('observability.home.sectionsubtitle', { - defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', - })} - + + {appsSection.map((app) => ( +
+ ))} + - - {/* Apps sections */} - - - - - {appsSection.map((app) => ( -
- ))} - - - - - - + + + - {/* Get started button */} - - - - - {i18n.translate('observability.home.getStatedButton', { - defaultMessage: 'Get started', - })} - - - + {/* Get started button */} + + + + + {i18n.translate('observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + - + - {/* Try it out */} - - - - -

- {i18n.translate('observability.home.tryItOut', { - defaultMessage: 'Try it out', - })} -

-
-
-
-
- - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - -
- - - ))} - - + {/* Try it out */} + + + + +

+ {i18n.translate('observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
- - - - +
+ + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + {}} + > +
+ + + ))} + + + + + + ); }; diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx index cd9c6adee18f0..f6ec695666074 100644 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ b/x-pack/plugins/observability/public/pages/home/section.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import styled from 'styled-components'; import { usePluginContext } from '../../hooks/use_plugin_context'; export interface ISection { @@ -25,6 +26,18 @@ export interface ISection { target?: '_blank'; } +const Link = styled(EuiButtonEmpty)` + height: auto; + &:focus { + background-color: transparent; + } + &:hover { + .title { + text-decoration: underline; + } + } +`; + export const Section = ({ section }: { section: ISection }) => { const { core } = usePluginContext(); const { id, icon, title, description, href } = section; @@ -35,7 +48,7 @@ export const Section = ({ section }: { section: ISection }) => { - +

{i18n.translate(`observability.section.${id}.title`, { defaultMessage: title, @@ -43,11 +56,13 @@ export const Section = ({ section }: { section: ISection }) => {

- - {i18n.translate(`observability.section.${id}.description`, { - defaultMessage: description, - })} - + {description && ( + + {i18n.translate(`observability.section.${id}.description`, { + defaultMessage: description, + })} + + )}
); @@ -55,13 +70,13 @@ export const Section = ({ section }: { section: ISection }) => { if (href) { return ( - {sectionContent} - + ); } From 79c7196e33c60d4aea6fda33392f9ed7a3010e63 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 2 Jun 2020 11:26:39 +0200 Subject: [PATCH 13/97] adding dark mode support --- x-pack/plugins/observability/public/application/index.tsx | 6 +++++- x-pack/plugins/observability/public/pages/home/index.tsx | 6 +++--- x-pack/plugins/observability/public/pages/home/section.tsx | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 805a0e1a07a9d..92d762714b8b1 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -5,14 +5,18 @@ */ import React from 'react'; import ReactDOM from 'react-dom'; +import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { Home } from '../pages/home'; import { PluginContext } from '../context/plugin_context'; export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { + const isDarkMode = core.uiSettings.get('theme:darkMode'); ReactDOM.render( - + + + , element ); diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index cd2455a4e0c72..72f11665ad7f4 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -74,12 +74,12 @@ const tryItOutItemsSection: ISection[] = [ const Container = styled.div` min-height: calc(100vh - 48px); - background: #fff; + background: ${(props) => props.theme.eui.euiColorEmptyShade}; `; const Title = styled.div` - background-color: #fafbfd; - border-bottom: 1px solid #d3dae6; + background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; + border-bottom: ${(props) => props.theme.eui.euiBorderThin}; `; const Page = styled.div` diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx index f6ec695666074..1d71662e01551 100644 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ b/x-pack/plugins/observability/public/pages/home/section.tsx @@ -38,6 +38,10 @@ const Link = styled(EuiButtonEmpty)` } `; +const Icon = styled(EuiIcon)` + color: ${(props) => props.theme.eui.euiIconColors.text}; +`; + export const Section = ({ section }: { section: ISection }) => { const { core } = usePluginContext(); const { id, icon, title, description, href } = section; @@ -45,7 +49,7 @@ export const Section = ({ section }: { section: ISection }) => { const sectionContent = ( - + From ceefa0e417a3d264873a8942f30e97f43aaa9bac Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 2 Jun 2020 11:30:40 +0200 Subject: [PATCH 14/97] fixing prettier format --- .../server/lib/annotations/bootstrap_annotations.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index f39b9440800ac..37e5ad42c3e7c 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -23,9 +23,7 @@ export type ScopedAnnotationsClientFactory = PromiseReturnType< typeof bootstrapAnnotations >['getScopedAnnotationsClient']; -export type ScopedAnnotationsClient = ReturnType< - ScopedAnnotationsClientFactory ->; +export type ScopedAnnotationsClient = ReturnType; export type AnnotationsAPI = PromiseReturnType; export async function bootstrapAnnotations({ index, core, context }: Params) { @@ -38,14 +36,10 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { }); return { - getScopedAnnotationsClient: ( - requestContext: RequestHandlerContext, - request: KibanaRequest - ) => { + getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { return createAnnotationsClient({ index, - apiCaller: - requestContext.core.elasticsearch.dataClient.callAsCurrentUser, + apiCaller: requestContext.core.elasticsearch.dataClient.callAsCurrentUser, logger, license: requestContext.licensing?.license, }); From 9f8998af882dab06b4376b8ceb2c7cc8fb2563a1 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 2 Jun 2020 13:25:15 +0200 Subject: [PATCH 15/97] fixing i18n --- .../public/application/index.tsx | 5 +- .../observability/public/pages/home/index.tsx | 62 ++++++++++++------- .../public/pages/home/section.tsx | 13 +--- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 92d762714b8b1..21a9fabf445f1 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -11,11 +11,14 @@ import { Home } from '../pages/home'; import { PluginContext } from '../context/plugin_context'; export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { + const i18nCore = core.i18n; const isDarkMode = core.uiSettings.get('theme:darkMode'); ReactDOM.render( - + + + , element diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 72f11665ad7f4..7880a28076661 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -26,38 +26,56 @@ import { usePluginContext } from '../../hooks/use_plugin_context'; const appsSection: ISection[] = [ { id: 'logs', - title: 'Logs', + title: i18n.translate('xpack.observability.section.apps.logs.title', { + defaultMessage: 'Logs', + }), icon: 'logoLogging', - description: - 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + description: i18n.translate('xpack.observability.section.apps.logs.description', { + defaultMessage: + 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + }), }, { id: 'apm', - title: 'APM', + title: i18n.translate('xpack.observability.section.apps.apm.title', { + defaultMessage: 'APM', + }), icon: 'logoAPM', - description: - 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + description: i18n.translate('xpack.observability.section.apps.apm.description', { + defaultMessage: + 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + }), }, { id: 'metrics', - title: 'Metrics', + title: i18n.translate('xpack.observability.section.apps.metrics.title', { + defaultMessage: 'Metrics', + }), icon: 'logoMetrics', - description: - 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + description: i18n.translate('xpack.observability.section.apps.metrics.description', { + defaultMessage: + 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + }), }, { id: 'uptime', - title: 'Uptime', + title: i18n.translate('xpack.observability.section.apps.uptime.title', { + defaultMessage: 'Uptime', + }), icon: 'logoUptime', - description: - 'React to availability issues across your apps and services before they affect users.', + description: i18n.translate('xpack.observability.section.apps.uptime.description', { + defaultMessage: + 'React to availability issues across your apps and services before they affect users.', + }), }, ]; const tryItOutItemsSection: ISection[] = [ { id: 'demo', - title: 'Demo Playground', + title: i18n.translate('xpack.observability.section.tryItOut.demo.title', { + defaultMessage: 'Demo Playground', + }), icon: 'play', description: '', href: 'https://demo.elastic.co/', @@ -65,7 +83,9 @@ const tryItOutItemsSection: ISection[] = [ }, { id: 'sampleData', - title: 'Add sample data', + title: i18n.translate('xpack.observability.section.tryItOut.sampleData.title', { + defaultMessage: 'Add sample data', + }), icon: 'documents', description: '', href: '/app/home#/tutorial_directory/sampleData', @@ -97,12 +117,12 @@ export const Home = () => { () => { core.chrome.setBreadcrumbs([ { - text: i18n.translate('observability.home.breadcrumb.observability', { + text: i18n.translate('xpack.observability.home.breadcrumb.observability', { defaultMessage: 'Observability', }), }, { - text: i18n.translate('observability.home.breadcrumb.gettingStarted', { + text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { defaultMessage: 'Getting started', }), }, @@ -124,7 +144,7 @@ export const Home = () => {

- {i18n.translate('observability.home.title', { + {i18n.translate('xpack.observability.home.title', { defaultMessage: 'Observability', })}

@@ -141,14 +161,14 @@ export const Home = () => {

- {i18n.translate('observability.home.sectionTitle', { + {i18n.translate('xpack.observability.home.sectionTitle', { defaultMessage: 'Observability built on the Elastic Stack', })}

- {i18n.translate('observability.home.sectionsubtitle', { + {i18n.translate('xpack.observability.home.sectionsubtitle', { defaultMessage: 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', })} @@ -189,7 +209,7 @@ export const Home = () => { style={{ width: 175 }} href={core.http.basePath.prepend('/app/home#/tutorial_directory/logging')} > - {i18n.translate('observability.home.getStatedButton', { + {i18n.translate('xpack.observability.home.getStatedButton', { defaultMessage: 'Get started', })} @@ -205,7 +225,7 @@ export const Home = () => {

- {i18n.translate('observability.home.tryItOut', { + {i18n.translate('xpack.observability.home.tryItOut', { defaultMessage: 'Try it out', })}

diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx index 1d71662e01551..c7d84ee9bbe5c 100644 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ b/x-pack/plugins/observability/public/pages/home/section.tsx @@ -12,7 +12,6 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; import { usePluginContext } from '../../hooks/use_plugin_context'; @@ -44,7 +43,7 @@ const Icon = styled(EuiIcon)` export const Section = ({ section }: { section: ISection }) => { const { core } = usePluginContext(); - const { id, icon, title, description, href } = section; + const { icon, title, description, href } = section; const sectionContent = ( @@ -53,18 +52,12 @@ export const Section = ({ section }: { section: ISection }) => {
-

- {i18n.translate(`observability.section.${id}.title`, { - defaultMessage: title, - })} -

+

{title}

{description && ( - {i18n.translate(`observability.section.${id}.description`, { - defaultMessage: description, - })} + {description} )}
From 435be86014ecc1e5854a03cf16d1538b3e91f354 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 2 Jun 2020 13:44:37 +0200 Subject: [PATCH 16/97] reverting some unnecessary changes --- .../public/components/action_menu.tsx | 8 ++--- .../public/hooks/use_track_metric.tsx | 24 +++---------- .../public/typings/eui_draggable/index.ts | 13 ++----- .../public/typings/eui_styled_components.tsx | 15 ++------ x-pack/plugins/observability/server/index.ts | 5 +-- .../lib/annotations/bootstrap_annotations.ts | 2 +- .../annotations/create_annotations_client.ts | 4 +-- .../annotations/register_annotation_apis.ts | 36 ++++++------------- x-pack/plugins/observability/server/plugin.ts | 5 +-- .../server/utils/create_or_update_index.ts | 3 +- .../plugins/observability/typings/common.ts | 4 +-- 11 files changed, 29 insertions(+), 90 deletions(-) diff --git a/x-pack/plugins/observability/public/components/action_menu.tsx b/x-pack/plugins/observability/public/components/action_menu.tsx index 9951dc69df15a..ea79f4d08d701 100644 --- a/x-pack/plugins/observability/public/components/action_menu.tsx +++ b/x-pack/plugins/observability/public/components/action_menu.tsx @@ -58,10 +58,6 @@ export const SectionLink: React.FC = (props) => ( ); -export const ActionMenuDivider: React.FC<{}> = (props) => ( - -); +export const ActionMenuDivider: React.FC<{}> = (props) => ; -export const ActionMenu: React.FC = (props) => ( - -); +export const ActionMenu: React.FC = (props) => ; diff --git a/x-pack/plugins/observability/public/hooks/use_track_metric.tsx b/x-pack/plugins/observability/public/hooks/use_track_metric.tsx index 61f0eef843e7c..b146a80e89ea8 100644 --- a/x-pack/plugins/observability/public/hooks/use_track_metric.tsx +++ b/x-pack/plugins/observability/public/hooks/use_track_metric.tsx @@ -38,14 +38,9 @@ export { METRIC_TYPE }; export function useUiTracker({ app: defaultApp, }: { app?: ObservabilityApp } = {}) { - const reportUiStats = useKibana().services?.usageCollection - ?.reportUiStats; + const reportUiStats = useKibana().services?.usageCollection?.reportUiStats; const trackEvent = useMemo(() => { - return ({ - app = defaultApp, - metric, - metricType = METRIC_TYPE.COUNT, - }: TrackMetricOptions) => { + return ({ app = defaultApp, metric, metricType = METRIC_TYPE.COUNT }: TrackMetricOptions) => { if (reportUiStats) { reportUiStats(app as string, metricType, metric); } @@ -55,16 +50,10 @@ export function useUiTracker({ } export function useTrackMetric( - { - app, - metric, - metricType = METRIC_TYPE.COUNT, - delay = 0, - }: TrackMetricOptions, + { app, metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions, effectDependencies: EffectDeps = [] ) { - const reportUiStats = useKibana().services?.usageCollection - ?.reportUiStats; + const reportUiStats = useKibana().services?.usageCollection?.reportUiStats; useEffect(() => { if (!reportUiStats) { @@ -99,8 +88,5 @@ export function useTrackPageview( { path, ...rest }: TrackPageviewProps, effectDependencies: EffectDeps = [] ) { - useTrackMetric( - { ...rest, metric: `pageview__${path}` }, - effectDependencies - ); + useTrackMetric({ ...rest, metric: `pageview__${path}` }, effectDependencies); } diff --git a/x-pack/plugins/observability/public/typings/eui_draggable/index.ts b/x-pack/plugins/observability/public/typings/eui_draggable/index.ts index 286b27182b50d..322966b3c982e 100644 --- a/x-pack/plugins/observability/public/typings/eui_draggable/index.ts +++ b/x-pack/plugins/observability/public/typings/eui_draggable/index.ts @@ -7,18 +7,11 @@ import React from 'react'; import { EuiDraggable, EuiDragDropContext } from '@elastic/eui'; -type PropsOf = T extends React.ComponentType - ? ComponentProps - : never; -type FirstArgumentOf = Func extends ( - arg1: infer FirstArgument, - ...rest: any[] -) => any +type PropsOf = T extends React.ComponentType ? ComponentProps : never; +type FirstArgumentOf = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any ? FirstArgument : never; export type DragHandleProps = FirstArgumentOf< Exclude['children'], React.ReactElement> >['dragHandleProps']; -export type DropResult = FirstArgumentOf< - FirstArgumentOf['onDragEnd'] ->; +export type DropResult = FirstArgumentOf['onDragEnd']>; diff --git a/x-pack/plugins/observability/public/typings/eui_styled_components.tsx b/x-pack/plugins/observability/public/typings/eui_styled_components.tsx index 020b3a7294b02..aab16f9d79c4b 100644 --- a/x-pack/plugins/observability/public/typings/eui_styled_components.tsx +++ b/x-pack/plugins/observability/public/typings/eui_styled_components.tsx @@ -6,11 +6,7 @@ import React from 'react'; import * as styledComponents from 'styled-components'; -import { - ThemedStyledComponentsModule, - ThemeProvider, - ThemeProviderProps, -} from 'styled-components'; +import { ThemedStyledComponentsModule, ThemeProvider, ThemeProviderProps } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; @@ -46,11 +42,4 @@ const { withTheme, } = (styledComponents as unknown) as ThemedStyledComponentsModule; -export { - css, - euiStyled, - EuiThemeProvider, - createGlobalStyle, - keyframes, - withTheme, -}; +export { css, euiStyled, EuiThemeProvider, createGlobalStyle, keyframes, withTheme }; diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 8fe8d3b2a3844..78550b781b411 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -7,10 +7,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from 'src/core/server'; import { ObservabilityPlugin, ObservabilityPluginSetup } from './plugin'; -import { - createOrUpdateIndex, - MappingsDefinition, -} from './utils/create_or_update_index'; +import { createOrUpdateIndex, MappingsDefinition } from './utils/create_or_update_index'; import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations'; export const config = { diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index 37e5ad42c3e7c..f57e1a774a8e2 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -39,7 +39,7 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { return createAnnotationsClient({ index, - apiCaller: requestContext.core.elasticsearch.dataClient.callAsCurrentUser, + apiCaller: requestContext.core.elasticsearch.legacy.client.callAsCurrentUser, logger, license: requestContext.licensing?.license, }); diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 35107278cd60e..71b1a42b2000d 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -57,9 +57,7 @@ export function createAnnotationsClient(params: { function ensureGoldLicense any>(fn: T): T { return ((...args) => { if (!license?.hasAtLeast('gold')) { - throw Boom.forbidden( - 'Annotations require at least a gold license or a trial license.' - ); + throw Boom.forbidden('Annotations require at least a gold license or a trial license.'); } return fn(...args); }) as T; diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 31c0bbdf3122d..5d0fdc65117bf 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -29,10 +29,7 @@ export function registerAnnotationAPIs({ }) { function wrapRouteHandler>( types: TType, - handler: (params: { - data: t.TypeOf; - client: ScopedAnnotationsClient; - }) => Promise + handler: (params: { data: t.TypeOf; client: ScopedAnnotationsClient }) => Promise ): RequestHandler { return async (...args: Parameters) => { const [context, request, response] = args; @@ -75,9 +72,7 @@ export function registerAnnotationAPIs({ return response.custom({ statusCode: err.output?.statusCode ?? 500, body: { - message: - err.output?.payload?.message ?? - 'An internal server error occured', + message: err.output?.payload?.message ?? 'An internal server error occured', }, }); } @@ -93,12 +88,9 @@ export function registerAnnotationAPIs({ body: unknowns, }, }, - wrapRouteHandler( - t.type({ body: createAnnotationRt }), - ({ data, client }) => { - return client.create(data.body); - } - ) + wrapRouteHandler(t.type({ body: createAnnotationRt }), ({ data, client }) => { + return client.create(data.body); + }) ); router.delete( @@ -108,12 +100,9 @@ export function registerAnnotationAPIs({ params: unknowns, }, }, - wrapRouteHandler( - t.type({ params: deleteAnnotationRt }), - ({ data, client }) => { - return client.delete(data.params); - } - ) + wrapRouteHandler(t.type({ params: deleteAnnotationRt }), ({ data, client }) => { + return client.delete(data.params); + }) ); router.get( @@ -123,11 +112,8 @@ export function registerAnnotationAPIs({ params: unknowns, }, }, - wrapRouteHandler( - t.type({ params: getAnnotationByIdRt }), - ({ data, client }) => { - return client.getById(data.params); - } - ) + wrapRouteHandler(t.type({ params: getAnnotationByIdRt }), ({ data, client }) => { + return client.getById(data.params); + }) ); } diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index d64e21d9e356b..ee328c4deb082 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -26,10 +26,7 @@ export class ObservabilityPlugin implements Plugin { this.initContext = initContext; } - public async setup( - core: CoreSetup, - plugins: {} - ): Promise { + public async setup(core: CoreSetup, plugins: {}): Promise { const config$ = this.initContext.config.create(); const config = await config$.pipe(take(1)).toPromise(); diff --git a/x-pack/plugins/observability/server/utils/create_or_update_index.ts b/x-pack/plugins/observability/server/utils/create_or_update_index.ts index f35cfc30de592..aa12b2227b7d2 100644 --- a/x-pack/plugins/observability/server/utils/create_or_update_index.ts +++ b/x-pack/plugins/observability/server/utils/create_or_update_index.ts @@ -57,8 +57,7 @@ export async function createOrUpdateIndex({ }); if (!result.acknowledged) { - const resultError = - result && result.error && JSON.stringify(result.error); + const resultError = result && result.error && JSON.stringify(result.error); throw new Error(resultError); } }, diff --git a/x-pack/plugins/observability/typings/common.ts b/x-pack/plugins/observability/typings/common.ts index 69593ccd50bc5..b4a90934a9f49 100644 --- a/x-pack/plugins/observability/typings/common.ts +++ b/x-pack/plugins/observability/typings/common.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export type PromiseReturnType = Func extends ( - ...args: any[] -) => Promise +export type PromiseReturnType = Func extends (...args: any[]) => Promise ? Value : Func; From 41ed1781353cbddfe17bb324773b858f39b7575a Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 3 Jun 2020 13:24:15 +0200 Subject: [PATCH 17/97] addressing PR comments --- .../public/assets/observability-overview.png | Bin 98273 -> 0 bytes .../observability/public/pages/home/index.tsx | 39 ++++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) delete mode 100644 x-pack/plugins/observability/public/assets/observability-overview.png diff --git a/x-pack/plugins/observability/public/assets/observability-overview.png b/x-pack/plugins/observability/public/assets/observability-overview.png deleted file mode 100644 index 70be08af9745ad4d7ab427d9b3d4c1d2d2f1e75a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98273 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�s 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

{^Fs1wC!ys~qb#haBPfuxcrj$rVVBk_t#x* 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 NE2St|D{dJ4{{R5buN43Q diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 7880a28076661..7c1e3a0962d5b 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -113,24 +113,20 @@ const Page = styled.div` export const Home = () => { const { core } = usePluginContext(); - useEffect( - () => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('xpack.observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('xpack.observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, [core]); return ( @@ -158,7 +154,7 @@ export const Home = () => { {/* title and description */} - +

{i18n.translate('xpack.observability.home.sectionTitle', { @@ -191,7 +187,7 @@ export const Home = () => { size="xl" alt="observability overview image" url={core.http.basePath.prepend( - '/plugins/observability/assets/observability-overview.png' + '/plugins/observability/assets/observability_overview.png' )} /> @@ -206,7 +202,6 @@ export const Home = () => { fill iconType="sortRight" iconSide="right" - style={{ width: 175 }} href={core.http.basePath.prepend('/app/home#/tutorial_directory/logging')} > {i18n.translate('xpack.observability.home.getStatedButton', { @@ -238,7 +233,7 @@ export const Home = () => { {tryItOutItemsSection.map((item) => ( - + Date: Wed, 3 Jun 2020 15:43:15 +0200 Subject: [PATCH 18/97] fixing functional tests --- .../feature_controls/index_patterns_security.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index a6d2c13cd2b31..bb4d093ff4fc6 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -71,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain('Stack Management'); + expect(navLinks).to.contain(['Stack Management']); }); it(`index pattern listing shows create button`, async () => { @@ -125,7 +125,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain('Stack Management'); + expect(navLinks).to.contain(['Stack Management']); }); it(`index pattern listing doesn't show create button`, async () => { From e0c590e7f13a772c399aa7249da7981081f8ac16 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 3 Jun 2020 15:48:42 +0200 Subject: [PATCH 19/97] ordering observability menu --- x-pack/plugins/observability/public/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 4888fe6b5fd43..f2c88a7b1c056 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -21,7 +21,7 @@ export class Plugin implements PluginClass { core.application.register({ id: 'observability-overview', title: 'Overview', - order: 7999, + order: 8000, appRoute: '/app/observability', category: DEFAULT_APP_CATEGORIES.observability, From 25cab4aa03601a3bfbf9d59b226993046ab6fbac Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 3 Jun 2020 17:12:08 +0200 Subject: [PATCH 20/97] fixing tests --- .../feature_controls/index_patterns_security.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index bb4d093ff4fc6..a6d2c13cd2b31 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -71,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing shows create button`, async () => { @@ -125,7 +125,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing doesn't show create button`, async () => { From 98a0e364afb918c3d246311adceee940c3e265b0 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 4 Jun 2020 11:20:19 +0200 Subject: [PATCH 21/97] addressing PR comments --- .../observability/public/pages/home/index.tsx | 26 +++--- .../public/pages/home/section.tsx | 82 +++++-------------- 2 files changed, 36 insertions(+), 72 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 7c1e3a0962d5b..f34504440dd3b 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -6,13 +6,13 @@ import { EuiButton, + EuiCard, EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiIcon, EuiImage, - EuiPanel, EuiSpacer, EuiText, EuiTitle, @@ -20,8 +20,8 @@ import { import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import styled from 'styled-components'; -import { ISection, Section } from './section'; import { usePluginContext } from '../../hooks/use_plugin_context'; +import { ISection, Section } from './section'; const appsSection: ISection[] = [ { @@ -233,15 +233,19 @@ export const Home = () => { {tryItOutItemsSection.map((item) => ( - - {}} - > -
- + + } + title={ + +

{item.title}

+
+ } + description={item.description} + target={item.target} + href={item.href} + />
))} diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx index c7d84ee9bbe5c..3b8c870e29ded 100644 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ b/x-pack/plugins/observability/public/pages/home/section.tsx @@ -3,18 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; -import { usePluginContext } from '../../hooks/use_plugin_context'; export interface ISection { id: string; @@ -25,57 +15,27 @@ export interface ISection { target?: '_blank'; } -const Link = styled(EuiButtonEmpty)` - height: auto; - &:focus { - background-color: transparent; - } - &:hover { - .title { - text-decoration: underline; - } - } -`; - -const Icon = styled(EuiIcon)` - color: ${(props) => props.theme.eui.euiIconColors.text}; -`; - export const Section = ({ section }: { section: ISection }) => { - const { core } = usePluginContext(); - const { icon, title, description, href } = section; + const { icon, title, description } = section; - const sectionContent = ( - - - - - - -

{title}

-
- - {description && ( - - {description} - - )} -
-
+ return ( + + + + + + + +

{title}

+
+ + {description && ( + + {description} + + )} +
+
+
); - - if (href) { - return ( - - - {sectionContent} - - - ); - } - return {sectionContent}; }; From 6c568b21e789d946c3e199b671df6e5e393ec828 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 4 Jun 2020 11:25:05 +0200 Subject: [PATCH 22/97] fixing scroll --- x-pack/plugins/observability/public/pages/home/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index f34504440dd3b..03c031bb4ffed 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -106,7 +106,7 @@ const Page = styled.div` width: 100%; max-width: 1200px; margin: 0 auto; - overflow-y: hidden; + overflow: hidden; } `; From 10889e5a956605428440efa7a62d3df770b94684 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 5 Jun 2020 09:28:20 +0200 Subject: [PATCH 23/97] addressing pr comments --- .../observability/public/pages/home/index.tsx | 90 ++++--------------- .../public/pages/home/section.tsx | 41 --------- 2 files changed, 19 insertions(+), 112 deletions(-) delete mode 100644 x-pack/plugins/observability/public/pages/home/section.tsx diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 03c031bb4ffed..c58fa0b56205e 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -21,76 +21,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { usePluginContext } from '../../hooks/use_plugin_context'; -import { ISection, Section } from './section'; - -const appsSection: ISection[] = [ - { - id: 'logs', - title: i18n.translate('xpack.observability.section.apps.logs.title', { - defaultMessage: 'Logs', - }), - icon: 'logoLogging', - description: i18n.translate('xpack.observability.section.apps.logs.description', { - defaultMessage: - 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', - }), - }, - { - id: 'apm', - title: i18n.translate('xpack.observability.section.apps.apm.title', { - defaultMessage: 'APM', - }), - icon: 'logoAPM', - description: i18n.translate('xpack.observability.section.apps.apm.description', { - defaultMessage: - 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', - }), - }, - { - id: 'metrics', - title: i18n.translate('xpack.observability.section.apps.metrics.title', { - defaultMessage: 'Metrics', - }), - icon: 'logoMetrics', - description: i18n.translate('xpack.observability.section.apps.metrics.description', { - defaultMessage: - 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', - }), - }, - { - id: 'uptime', - title: i18n.translate('xpack.observability.section.apps.uptime.title', { - defaultMessage: 'Uptime', - }), - icon: 'logoUptime', - description: i18n.translate('xpack.observability.section.apps.uptime.description', { - defaultMessage: - 'React to availability issues across your apps and services before they affect users.', - }), - }, -]; - -const tryItOutItemsSection: ISection[] = [ - { - id: 'demo', - title: i18n.translate('xpack.observability.section.tryItOut.demo.title', { - defaultMessage: 'Demo Playground', - }), - icon: 'play', - description: '', - href: 'https://demo.elastic.co/', - target: '_blank', - }, - { - id: 'sampleData', - title: i18n.translate('xpack.observability.section.tryItOut.sampleData.title', { - defaultMessage: 'Add sample data', - }), - icon: 'documents', - description: '', - href: '/app/home#/tutorial_directory/sampleData', - }, -]; +import { appsSection, tryItOutItemsSection } from './section'; const Container = styled.div` min-height: calc(100vh - 48px); @@ -110,6 +41,12 @@ const Page = styled.div` } `; +const EuiCardWithoutBorder = styled(EuiCard)` + border: none; + box-shadow: none; + padding: 0; +`; + export const Home = () => { const { core } = usePluginContext(); @@ -178,7 +115,18 @@ export const Home = () => { {appsSection.map((app) => ( -
+ + } + title={ + +

{app.title}

+
+ } + description={app.description} + /> +
))} diff --git a/x-pack/plugins/observability/public/pages/home/section.tsx b/x-pack/plugins/observability/public/pages/home/section.tsx deleted file mode 100644 index 3b8c870e29ded..0000000000000 --- a/x-pack/plugins/observability/public/pages/home/section.tsx +++ /dev/null @@ -1,41 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import React from 'react'; - -export interface ISection { - id: string; - title: string; - icon: string; - description: string; - href?: string; - target?: '_blank'; -} - -export const Section = ({ section }: { section: ISection }) => { - const { icon, title, description } = section; - - return ( - - - - - - - -

{title}

-
- - {description && ( - - {description} - - )} -
-
-
- ); -}; From 843a23c51c54123604e083e717cb441d85b814d1 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 5 Jun 2020 10:50:19 +0200 Subject: [PATCH 24/97] addressing pr comments --- x-pack/plugins/observability/public/pages/home/index.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index c58fa0b56205e..b9b567bef4ab4 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -41,9 +41,7 @@ const Page = styled.div` } `; -const EuiCardWithoutBorder = styled(EuiCard)` - border: none; - box-shadow: none; +const EuiCardWithoutPadding = styled(EuiCard)` padding: 0; `; @@ -116,7 +114,8 @@ export const Home = () => { {appsSection.map((app) => ( - } title={ From 96cf83f7f5ead0b7e8ae83783bbe63d64ef2c875 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Sat, 6 Jun 2020 13:09:39 +0200 Subject: [PATCH 25/97] creating overview page --- .../public/application/index.tsx | 27 +++++-- .../index.tsx} | 0 .../public/components/chart/container.tsx | 43 +++++++++++ .../public/components/empty_section/index.tsx | 38 ++++++++++ .../public/components/header/index.tsx | 51 +++++++++++++ .../public/components/layout/with_header.tsx | 32 ++++++++ x-pack/plugins/observability/public/index.ts | 2 +- .../observability/public/pages/home/index.tsx | 4 +- .../public/pages/home/section.ts | 30 +++++++- .../pages/overview/continue_journey.tsx | 38 ++++++++++ .../public/pages/overview/index.tsx | 74 +++++++++++++++++++ 11 files changed, 331 insertions(+), 8 deletions(-) rename x-pack/plugins/observability/public/components/{action_menu.tsx => action_menu/index.tsx} (100%) create mode 100644 x-pack/plugins/observability/public/components/chart/container.tsx create mode 100644 x-pack/plugins/observability/public/components/empty_section/index.tsx create mode 100644 x-pack/plugins/observability/public/components/header/index.tsx create mode 100644 x-pack/plugins/observability/public/components/layout/with_header.tsx create mode 100644 x-pack/plugins/observability/public/pages/overview/continue_journey.tsx create mode 100644 x-pack/plugins/observability/public/pages/overview/index.tsx diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 21a9fabf445f1..f287aa97482b8 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -5,21 +5,38 @@ */ import React from 'react'; import ReactDOM from 'react-dom'; +import { Route, Router, Switch } from 'react-router-dom'; +import { createHashHistory } from 'history'; import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { Home } from '../pages/home'; +import { Overview } from '../pages/overview'; import { PluginContext } from '../context/plugin_context'; +const App = () => { + return ( + <> + + + + + + ); +}; + export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { const i18nCore = core.i18n; const isDarkMode = core.uiSettings.get('theme:darkMode'); + const history = createHashHistory(); ReactDOM.render( - - - - - + + + + + + + , element ); diff --git a/x-pack/plugins/observability/public/components/action_menu.tsx b/x-pack/plugins/observability/public/components/action_menu/index.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/action_menu.tsx rename to x-pack/plugins/observability/public/components/action_menu/index.tsx diff --git a/x-pack/plugins/observability/public/components/chart/container.tsx b/x-pack/plugins/observability/public/components/chart/container.tsx new file mode 100644 index 0000000000000..6f80a27f754e4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/chart/container.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiAccordion, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { EuiSpacer } from '@elastic/eui'; + +interface Props { + title: string; + children: React.ReactNode; +} + +const Container = styled.div` + .accordion-button { + width: 100%; + } +`; + +export const ChartContainer = ({ title, children }: Props) => { + return ( + + + +
{title}
+
+ + + } + > + + {children} +
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/empty_section/index.tsx b/x-pack/plugins/observability/public/components/empty_section/index.tsx new file mode 100644 index 0000000000000..73d7cb4b0d89b --- /dev/null +++ b/x-pack/plugins/observability/public/components/empty_section/index.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useContext } from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; +import { ThemeContext } from 'styled-components'; +import { EuiText } from '@elastic/eui'; +import { ISection } from '../../pages/home/section'; + +interface Props { + section: ISection; +} + +export const EmptySection = ({ section }: Props) => { + const theme = useContext(ThemeContext); + return ( + {section.title}

} + titleSize="xs" + body={{section.description}} + actions={ + <> + {section.linkTitle && ( + + {section.linkTitle} + + )} + + } + /> + ); +}; diff --git a/x-pack/plugins/observability/public/components/header/index.tsx b/x-pack/plugins/observability/public/components/header/index.tsx new file mode 100644 index 0000000000000..828afdf1c9099 --- /dev/null +++ b/x-pack/plugins/observability/public/components/header/index.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; + +const Container = styled.div<{ color: string }>` + background: ${(props) => props.color}; + border-bottom: ${(props) => props.theme.eui.euiBorderThin}; +`; + +const Wrapper = styled.div<{ maxWidth?: number }>` + width: 100%; + max-width: 1200px; + margin: 0 auto; + overflow: hidden; +`; + +interface Props { + color: string; +} + +export const Header = ({ color }: Props) => { + return ( + + + + + + + + + +

+ {i18n.translate('xpack.observability.home.title', { + defaultMessage: 'Observability', + })} +

+
+
+
+ +
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/layout/with_header.tsx b/x-pack/plugins/observability/public/components/layout/with_header.tsx new file mode 100644 index 0000000000000..07c7b39002f9c --- /dev/null +++ b/x-pack/plugins/observability/public/components/layout/with_header.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; +import { EuiPage, EuiPageBody, EuiSpacer, EuiPageProps } from '@elastic/eui'; +import { Header } from '../header/index'; + +const Page = styled(EuiPage)` + background: ${(props) => props.color}; +`; + +interface Props { + headerColor: string; + bodyColor: string; + children?: React.ReactNode; +} + +export const WithHeaderLayout = ({ headerColor, bodyColor, children }: Props) => ( + <> +
+ + + + {children} + + + +); diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index dd8bca90cff4f..0a5913da5758f 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -13,7 +13,7 @@ export const plugin: PluginInitializer = ( return new Plugin(context); }; -export * from './components/action_menu'; +export * from './components/action_menu/'; export { useTrackPageview, diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index b9b567bef4ab4..4967bcb7a7618 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -63,6 +63,8 @@ export const Home = () => { ]); }, [core]); + const apps = appsSection.filter((app) => app.id !== 'alert'); + return ( @@ -112,7 +114,7 @@ export const Home = () => { <EuiFlexGroup> <EuiFlexItem> <EuiFlexGrid columns={2}> - {appsSection.map((app) => ( + {apps.map((app) => ( <EuiFlexItem> <EuiCardWithoutPadding display="plain" diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index f8bbfbfa30548..074f00572e26c 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -5,12 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -interface ISection { +export interface ISection { id: string; title: string; icon: string; description: string; href?: string; + linkTitle?: string; target?: '_blank'; } @@ -25,6 +26,10 @@ export const appsSection: ISection[] = [ defaultMessage: 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', }), + linkTitle: i18n.translate('xpack.observability.section.apps.logs.link', { + defaultMessage: 'Install Filebeat', + }), + href: 'https://www.elastic.co', }, { id: 'apm', @@ -36,6 +41,10 @@ export const appsSection: ISection[] = [ defaultMessage: 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', }), + linkTitle: i18n.translate('xpack.observability.section.apps.apm.link', { + defaultMessage: 'Install agent', + }), + href: 'https://www.elastic.co', }, { id: 'metrics', @@ -47,6 +56,10 @@ export const appsSection: ISection[] = [ defaultMessage: 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', }), + linkTitle: i18n.translate('xpack.observability.section.apps.metrics.link', { + defaultMessage: 'Install metrics module', + }), + href: 'https://www.elastic.co', }, { id: 'uptime', @@ -58,7 +71,22 @@ export const appsSection: ISection[] = [ defaultMessage: 'React to availability issues across your apps and services before they affect users.', }), + linkTitle: i18n.translate('xpack.observability.section.apps.uptime.link', { + defaultMessage: 'Install Heartbeat', + }), + href: 'https://www.elastic.co', }, + // { + // id: 'alert', + // title: i18n.translate('xpack.observability.section.apps.alert.title', { + // defaultMessage: 'Alert', + // }), + // icon: 'watchesApp', + // description: i18n.translate('xpack.observability.section.apps.alert.description', { + // defaultMessage: + // '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', + // }), + // }, ]; export const tryItOutItemsSection: ISection[] = [ diff --git a/x-pack/plugins/observability/public/pages/overview/continue_journey.tsx b/x-pack/plugins/observability/public/pages/overview/continue_journey.tsx new file mode 100644 index 0000000000000..9c55d7576626c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/continue_journey.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGrid, EuiFlexItem, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EmptySection } from '../../components/empty_section'; +import { ISection } from '../home/section'; + +interface Props { + apps: ISection[]; +} + +export const ContinueJourney = ({ apps }: Props) => { + return ( + <> + <EuiTitle size="s"> + <h5> + {i18n.translate('xpack.observability.continueJorney.title', { + defaultMessage: 'Continue your Observability journey...', + })} + </h5> + </EuiTitle> + <EuiHorizontalRule margin="m" /> + <EuiFlexGrid columns={2}> + {apps.map((app) => { + return ( + <EuiFlexItem key={app.id}> + <EmptySection section={app} /> + </EuiFlexItem> + ); + })} + </EuiFlexGrid> + </> + ); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx new file mode 100644 index 0000000000000..c6bc6cf5676e5 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import React, { useContext, useState } from 'react'; +import { ThemeContext } from 'styled-components'; +import { EuiFlexGrid } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; +import { EuiSwitch } from '@elastic/eui'; +import { ChartContainer } from '../../components/chart/container'; +import { WithHeaderLayout } from '../../components/layout/with_header'; +import { ContinueJourney } from './continue_journey'; +import { appsSection } from '../home/section'; + +export const Overview = () => { + const theme = useContext(ThemeContext); + const [withAlert, setWithAlert] = useState(true); + + return ( + <WithHeaderLayout + headerColor={theme.eui.euiColorEmptyShade} + bodyColor={theme.eui.euiPageBackgroundColor} + > + <EuiSwitch + label="With alert data?" + checked={withAlert} + onChange={(e) => setWithAlert((currState) => !currState)} + /> + <EuiSpacer /> + <EuiFlexGrid columns={withAlert ? 2 : 1}> + <EuiFlexItem> + <EuiFlexGroup direction="column"> + <EuiFlexItem> + <ChartContainer title="Logs"> + <EuiPanel>chart goes here</EuiPanel> + </ChartContainer> + </EuiFlexItem> + <EuiFlexItem> + <ChartContainer title="Metrics"> + <EuiPanel>chart goes here</EuiPanel> + </ChartContainer> + </EuiFlexItem> + <EuiFlexItem> + <ChartContainer title="APM"> + <EuiPanel>chart goes here</EuiPanel> + </ChartContainer> + </EuiFlexItem> + <EuiFlexItem> + <ChartContainer title="Uptime"> + <EuiPanel>chart goes here</EuiPanel> + </ChartContainer> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + + {withAlert && ( + <EuiFlexItem> + <ChartContainer title="alert"> + <EuiPanel>chart goes here</EuiPanel> + </ChartContainer> + </EuiFlexItem> + )} + </EuiFlexGrid> + + <EuiSpacer /> + + <EuiFlexItem> + <ContinueJourney apps={appsSection} /> + </EuiFlexItem> + </WithHeaderLayout> + ); +}; From 3c566eaeceed18a5f29119eba29e2525cc745efe Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Mon, 8 Jun 2020 09:29:46 +0200 Subject: [PATCH 26/97] mocking data --- .../public/components/chart/container.tsx | 5 +- .../public/pages/overview/apm.data.ts | 2914 +++++++++++++++++ .../public/pages/overview/index.tsx | 225 +- .../public/pages/overview/logs.data.ts | 333 ++ .../public/pages/overview/uptime.data.ts | 168 + 5 files changed, 3632 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/overview/apm.data.ts create mode 100644 x-pack/plugins/observability/public/pages/overview/logs.data.ts create mode 100644 x-pack/plugins/observability/public/pages/overview/uptime.data.ts diff --git a/x-pack/plugins/observability/public/components/chart/container.tsx b/x-pack/plugins/observability/public/components/chart/container.tsx index 6f80a27f754e4..f034412e20738 100644 --- a/x-pack/plugins/observability/public/components/chart/container.tsx +++ b/x-pack/plugins/observability/public/components/chart/container.tsx @@ -3,10 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiAccordion, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; +import { EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { EuiSpacer } from '@elastic/eui'; interface Props { title: string; @@ -36,7 +35,7 @@ export const ChartContainer = ({ title, children }: Props) => { } > <EuiSpacer size="s" /> - {children} + <EuiPanel style={{ height: '296px' }}>{children}</EuiPanel> </EuiAccordion> </Container> ); diff --git a/x-pack/plugins/observability/public/pages/overview/apm.data.ts b/x-pack/plugins/observability/public/pages/overview/apm.data.ts new file mode 100644 index 0000000000000..ea364ba7f4189 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/apm.data.ts @@ -0,0 +1,2914 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const transactions = [ + { + key_as_string: '2020-06-05T14:00:00.000Z', + key: 1591365600000, + doc_count: 32, + }, + { + key_as_string: '2020-06-05T14:10:00.000Z', + key: 1591366200000, + doc_count: 43, + }, + { + key_as_string: '2020-06-05T14:20:00.000Z', + key: 1591366800000, + doc_count: 22, + }, + { + key_as_string: '2020-06-05T14:30:00.000Z', + key: 1591367400000, + doc_count: 29, + }, + { + key_as_string: '2020-06-05T14:40:00.000Z', + key: 1591368000000, + doc_count: 39, + }, + { + key_as_string: '2020-06-05T14:50:00.000Z', + key: 1591368600000, + doc_count: 36, + }, + { + key_as_string: '2020-06-05T15:00:00.000Z', + key: 1591369200000, + doc_count: 50, + }, + { + key_as_string: '2020-06-05T15:10:00.000Z', + key: 1591369800000, + doc_count: 31, + }, + { + key_as_string: '2020-06-05T15:20:00.000Z', + key: 1591370400000, + doc_count: 39, + }, + { + key_as_string: '2020-06-05T15:30:00.000Z', + key: 1591371000000, + doc_count: 26, + }, + { + key_as_string: '2020-06-05T15:40:00.000Z', + key: 1591371600000, + doc_count: 45, + }, + { + key_as_string: '2020-06-05T15:50:00.000Z', + key: 1591372200000, + doc_count: 27, + }, + { + key_as_string: '2020-06-05T16:00:00.000Z', + key: 1591372800000, + doc_count: 37, + }, + { + key_as_string: '2020-06-05T16:10:00.000Z', + key: 1591373400000, + doc_count: 55, + }, + { + key_as_string: '2020-06-05T16:20:00.000Z', + key: 1591374000000, + doc_count: 31, + }, + { + key_as_string: '2020-06-05T16:30:00.000Z', + key: 1591374600000, + doc_count: 26, + }, + { + key_as_string: '2020-06-05T16:40:00.000Z', + key: 1591375200000, + doc_count: 57, + }, + { + key_as_string: '2020-06-05T16:50:00.000Z', + key: 1591375800000, + doc_count: 25, + }, + { + key_as_string: '2020-06-05T17:00:00.000Z', + key: 1591376400000, + doc_count: 28, + }, + { + key_as_string: '2020-06-05T17:10:00.000Z', + key: 1591377000000, + doc_count: 40, + }, + { + key_as_string: '2020-06-05T17:20:00.000Z', + key: 1591377600000, + doc_count: 33, + }, + { + key_as_string: '2020-06-05T17:30:00.000Z', + key: 1591378200000, + doc_count: 33, + }, + { + key_as_string: '2020-06-05T17:40:00.000Z', + key: 1591378800000, + doc_count: 31, + }, + { + key_as_string: '2020-06-05T17:50:00.000Z', + key: 1591379400000, + doc_count: 32, + }, + { + key_as_string: '2020-06-05T18:00:00.000Z', + key: 1591380000000, + doc_count: 34, + }, + { + key_as_string: '2020-06-05T18:10:00.000Z', + key: 1591380600000, + doc_count: 31, + }, + { + key_as_string: '2020-06-05T18:20:00.000Z', + key: 1591381200000, + doc_count: 16, + }, + { + key_as_string: '2020-06-05T18:30:00.000Z', + key: 1591381800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-05T18:40:00.000Z', + key: 1591382400000, + doc_count: 33, + }, + { + key_as_string: '2020-06-05T18:50:00.000Z', + key: 1591383000000, + doc_count: 35, + }, + { + key_as_string: '2020-06-05T19:00:00.000Z', + key: 1591383600000, + doc_count: 47, + }, + { + key_as_string: '2020-06-05T19:10:00.000Z', + key: 1591384200000, + doc_count: 44, + }, + { + key_as_string: '2020-06-05T19:20:00.000Z', + key: 1591384800000, + doc_count: 21, + }, + { + key_as_string: '2020-06-05T19:30:00.000Z', + key: 1591385400000, + doc_count: 25, + }, + { + key_as_string: '2020-06-05T19:40:00.000Z', + key: 1591386000000, + doc_count: 34, + }, + { + key_as_string: '2020-06-05T19:50:00.000Z', + key: 1591386600000, + doc_count: 37, + }, + { + key_as_string: '2020-06-05T20:00:00.000Z', + key: 1591387200000, + doc_count: 38, + }, + { + key_as_string: '2020-06-05T20:10:00.000Z', + key: 1591387800000, + doc_count: 28, + }, + { + key_as_string: '2020-06-05T20:20:00.000Z', + key: 1591388400000, + doc_count: 32, + }, + { + key_as_string: '2020-06-05T20:30:00.000Z', + key: 1591389000000, + doc_count: 37, + }, + { + key_as_string: '2020-06-05T20:40:00.000Z', + key: 1591389600000, + doc_count: 25, + }, + { + key_as_string: '2020-06-05T20:50:00.000Z', + key: 1591390200000, + doc_count: 33, + }, + { + key_as_string: '2020-06-05T21:00:00.000Z', + key: 1591390800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-05T21:10:00.000Z', + key: 1591391400000, + doc_count: 30, + }, + { + key_as_string: '2020-06-05T21:20:00.000Z', + key: 1591392000000, + doc_count: 45, + }, + { + key_as_string: '2020-06-05T21:30:00.000Z', + key: 1591392600000, + doc_count: 42, + }, + { + key_as_string: '2020-06-05T21:40:00.000Z', + key: 1591393200000, + doc_count: 23, + }, + { + key_as_string: '2020-06-05T21:50:00.000Z', + key: 1591393800000, + doc_count: 33, + }, + { + key_as_string: '2020-06-05T22:00:00.000Z', + key: 1591394400000, + doc_count: 38, + }, + { + key_as_string: '2020-06-05T22:10:00.000Z', + key: 1591395000000, + doc_count: 30, + }, + { + key_as_string: '2020-06-05T22:20:00.000Z', + key: 1591395600000, + doc_count: 25, + }, + { + key_as_string: '2020-06-05T22:30:00.000Z', + key: 1591396200000, + doc_count: 33, + }, + { + key_as_string: '2020-06-05T22:40:00.000Z', + key: 1591396800000, + doc_count: 37, + }, + { + key_as_string: '2020-06-05T22:50:00.000Z', + key: 1591397400000, + doc_count: 43, + }, + { + key_as_string: '2020-06-05T23:00:00.000Z', + key: 1591398000000, + doc_count: 30, + }, + { + key_as_string: '2020-06-05T23:10:00.000Z', + key: 1591398600000, + doc_count: 36, + }, + { + key_as_string: '2020-06-05T23:20:00.000Z', + key: 1591399200000, + doc_count: 28, + }, + { + key_as_string: '2020-06-05T23:30:00.000Z', + key: 1591399800000, + doc_count: 39, + }, + { + key_as_string: '2020-06-05T23:40:00.000Z', + key: 1591400400000, + doc_count: 27, + }, + { + key_as_string: '2020-06-05T23:50:00.000Z', + key: 1591401000000, + doc_count: 41, + }, + { + key_as_string: '2020-06-06T00:00:00.000Z', + key: 1591401600000, + doc_count: 25, + }, + { + key_as_string: '2020-06-06T00:10:00.000Z', + key: 1591402200000, + doc_count: 31, + }, + { + key_as_string: '2020-06-06T00:20:00.000Z', + key: 1591402800000, + doc_count: 28, + }, + { + key_as_string: '2020-06-06T00:30:00.000Z', + key: 1591403400000, + doc_count: 29, + }, + { + key_as_string: '2020-06-06T00:40:00.000Z', + key: 1591404000000, + doc_count: 49, + }, + { + key_as_string: '2020-06-06T00:50:00.000Z', + key: 1591404600000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T01:00:00.000Z', + key: 1591405200000, + doc_count: 41, + }, + { + key_as_string: '2020-06-06T01:10:00.000Z', + key: 1591405800000, + doc_count: 30, + }, + { + key_as_string: '2020-06-06T01:20:00.000Z', + key: 1591406400000, + doc_count: 36, + }, + { + key_as_string: '2020-06-06T01:30:00.000Z', + key: 1591407000000, + doc_count: 39, + }, + { + key_as_string: '2020-06-06T01:40:00.000Z', + key: 1591407600000, + doc_count: 23, + }, + { + key_as_string: '2020-06-06T01:50:00.000Z', + key: 1591408200000, + doc_count: 40, + }, + { + key_as_string: '2020-06-06T02:00:00.000Z', + key: 1591408800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T02:10:00.000Z', + key: 1591409400000, + doc_count: 28, + }, + { + key_as_string: '2020-06-06T02:20:00.000Z', + key: 1591410000000, + doc_count: 33, + }, + { + key_as_string: '2020-06-06T02:30:00.000Z', + key: 1591410600000, + doc_count: 31, + }, + { + key_as_string: '2020-06-06T02:40:00.000Z', + key: 1591411200000, + doc_count: 39, + }, + { + key_as_string: '2020-06-06T02:50:00.000Z', + key: 1591411800000, + doc_count: 33, + }, + { + key_as_string: '2020-06-06T03:00:00.000Z', + key: 1591412400000, + doc_count: 35, + }, + { + key_as_string: '2020-06-06T03:10:00.000Z', + key: 1591413000000, + doc_count: 31, + }, + { + key_as_string: '2020-06-06T03:20:00.000Z', + key: 1591413600000, + doc_count: 35, + }, + { + key_as_string: '2020-06-06T03:30:00.000Z', + key: 1591414200000, + doc_count: 37, + }, + { + key_as_string: '2020-06-06T03:40:00.000Z', + key: 1591414800000, + doc_count: 26, + }, + { + key_as_string: '2020-06-06T03:50:00.000Z', + key: 1591415400000, + doc_count: 27, + }, + { + key_as_string: '2020-06-06T04:00:00.000Z', + key: 1591416000000, + doc_count: 26, + }, + { + key_as_string: '2020-06-06T04:10:00.000Z', + key: 1591416600000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T04:20:00.000Z', + key: 1591417200000, + doc_count: 33, + }, + { + key_as_string: '2020-06-06T04:30:00.000Z', + key: 1591417800000, + doc_count: 38, + }, + { + key_as_string: '2020-06-06T04:40:00.000Z', + key: 1591418400000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T04:50:00.000Z', + key: 1591419000000, + doc_count: 37, + }, + { + key_as_string: '2020-06-06T05:00:00.000Z', + key: 1591419600000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T05:10:00.000Z', + key: 1591420200000, + doc_count: 25, + }, + { + key_as_string: '2020-06-06T05:20:00.000Z', + key: 1591420800000, + doc_count: 20, + }, + { + key_as_string: '2020-06-06T05:30:00.000Z', + key: 1591421400000, + doc_count: 35, + }, + { + key_as_string: '2020-06-06T05:40:00.000Z', + key: 1591422000000, + doc_count: 41, + }, + { + key_as_string: '2020-06-06T05:50:00.000Z', + key: 1591422600000, + doc_count: 40, + }, + { + key_as_string: '2020-06-06T06:00:00.000Z', + key: 1591423200000, + doc_count: 33, + }, + { + key_as_string: '2020-06-06T06:10:00.000Z', + key: 1591423800000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T06:20:00.000Z', + key: 1591424400000, + doc_count: 44, + }, + { + key_as_string: '2020-06-06T06:30:00.000Z', + key: 1591425000000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T06:40:00.000Z', + key: 1591425600000, + doc_count: 32, + }, + { + key_as_string: '2020-06-06T06:50:00.000Z', + key: 1591426200000, + doc_count: 37, + }, + { + key_as_string: '2020-06-06T07:00:00.000Z', + key: 1591426800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T07:10:00.000Z', + key: 1591427400000, + doc_count: 28, + }, + { + key_as_string: '2020-06-06T07:20:00.000Z', + key: 1591428000000, + doc_count: 26, + }, + { + key_as_string: '2020-06-06T07:30:00.000Z', + key: 1591428600000, + doc_count: 37, + }, + { + key_as_string: '2020-06-06T07:40:00.000Z', + key: 1591429200000, + doc_count: 36, + }, + { + key_as_string: '2020-06-06T07:50:00.000Z', + key: 1591429800000, + doc_count: 37, + }, + { + key_as_string: '2020-06-06T08:00:00.000Z', + key: 1591430400000, + doc_count: 23, + }, + { + key_as_string: '2020-06-06T08:10:00.000Z', + key: 1591431000000, + doc_count: 47, + }, + { + key_as_string: '2020-06-06T08:20:00.000Z', + key: 1591431600000, + doc_count: 41, + }, + { + key_as_string: '2020-06-06T08:30:00.000Z', + key: 1591432200000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T08:40:00.000Z', + key: 1591432800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T08:50:00.000Z', + key: 1591433400000, + doc_count: 27, + }, + { + key_as_string: '2020-06-06T09:00:00.000Z', + key: 1591434000000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T09:10:00.000Z', + key: 1591434600000, + doc_count: 44, + }, + { + key_as_string: '2020-06-06T09:20:00.000Z', + key: 1591435200000, + doc_count: 20, + }, + { + key_as_string: '2020-06-06T09:30:00.000Z', + key: 1591435800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T09:40:00.000Z', + key: 1591436400000, + doc_count: 29, + }, + { + key_as_string: '2020-06-06T09:50:00.000Z', + key: 1591437000000, + doc_count: 28, + }, + { + key_as_string: '2020-06-06T10:00:00.000Z', + key: 1591437600000, + doc_count: 36, + }, + { + key_as_string: '2020-06-06T10:10:00.000Z', + key: 1591438200000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T10:20:00.000Z', + key: 1591438800000, + doc_count: 26, + }, + { + key_as_string: '2020-06-06T10:30:00.000Z', + key: 1591439400000, + doc_count: 29, + }, + { + key_as_string: '2020-06-06T10:40:00.000Z', + key: 1591440000000, + doc_count: 45, + }, + { + key_as_string: '2020-06-06T10:50:00.000Z', + key: 1591440600000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T11:00:00.000Z', + key: 1591441200000, + doc_count: 25, + }, + { + key_as_string: '2020-06-06T11:10:00.000Z', + key: 1591441800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T11:20:00.000Z', + key: 1591442400000, + doc_count: 28, + }, + { + key_as_string: '2020-06-06T11:30:00.000Z', + key: 1591443000000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T11:40:00.000Z', + key: 1591443600000, + doc_count: 31, + }, + { + key_as_string: '2020-06-06T11:50:00.000Z', + key: 1591444200000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T12:00:00.000Z', + key: 1591444800000, + doc_count: 34, + }, + { + key_as_string: '2020-06-06T12:10:00.000Z', + key: 1591445400000, + doc_count: 21, + }, + { + key_as_string: '2020-06-06T12:20:00.000Z', + key: 1591446000000, + doc_count: 40, + }, + { + key_as_string: '2020-06-06T12:30:00.000Z', + key: 1591446600000, + doc_count: 37, + }, + { + key_as_string: '2020-06-06T12:40:00.000Z', + key: 1591447200000, + doc_count: 31, + }, + { + key_as_string: '2020-06-06T12:50:00.000Z', + key: 1591447800000, + doc_count: 21, + }, + { + key_as_string: '2020-06-06T13:00:00.000Z', + key: 1591448400000, + doc_count: 24, + }, + { + key_as_string: '2020-06-06T13:10:00.000Z', + key: 1591449000000, + doc_count: 30, + }, + { + key_as_string: '2020-06-06T13:20:00.000Z', + key: 1591449600000, + doc_count: 22, + }, + { + key_as_string: '2020-06-06T13:30:00.000Z', + key: 1591450200000, + doc_count: 27, + }, + { + key_as_string: '2020-06-06T13:40:00.000Z', + key: 1591450800000, + doc_count: 30, + }, + { + key_as_string: '2020-06-06T13:50:00.000Z', + key: 1591451400000, + doc_count: 22, + }, + { + key_as_string: '2020-06-06T14:00:00.000Z', + key: 1591452000000, + doc_count: 9, + }, +]; + +export const errors = [ + { + key_as_string: '2020-06-05T14:00:00.000Z', + key: 1591365600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T14:10:00.000Z', + key: 1591366200000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T14:20:00.000Z', + key: 1591366800000, + doc_count: 1, + }, + { + key_as_string: '2020-06-05T14:30:00.000Z', + key: 1591367400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T14:40:00.000Z', + key: 1591368000000, + doc_count: 8, + }, + { + key_as_string: '2020-06-05T14:50:00.000Z', + key: 1591368600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T15:00:00.000Z', + key: 1591369200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T15:10:00.000Z', + key: 1591369800000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T15:20:00.000Z', + key: 1591370400000, + doc_count: 9, + }, + { + key_as_string: '2020-06-05T15:30:00.000Z', + key: 1591371000000, + doc_count: 1, + }, + { + key_as_string: '2020-06-05T15:40:00.000Z', + key: 1591371600000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T15:50:00.000Z', + key: 1591372200000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T16:00:00.000Z', + key: 1591372800000, + doc_count: 8, + }, + { + key_as_string: '2020-06-05T16:10:00.000Z', + key: 1591373400000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T16:20:00.000Z', + key: 1591374000000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T16:30:00.000Z', + key: 1591374600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T16:40:00.000Z', + key: 1591375200000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T16:50:00.000Z', + key: 1591375800000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T17:00:00.000Z', + key: 1591376400000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T17:10:00.000Z', + key: 1591377000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T17:20:00.000Z', + key: 1591377600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T17:30:00.000Z', + key: 1591378200000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T17:40:00.000Z', + key: 1591378800000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T17:50:00.000Z', + key: 1591379400000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T18:00:00.000Z', + key: 1591380000000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T18:10:00.000Z', + key: 1591380600000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T18:20:00.000Z', + key: 1591381200000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T18:30:00.000Z', + key: 1591381800000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T18:40:00.000Z', + key: 1591382400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T18:50:00.000Z', + key: 1591383000000, + doc_count: 8, + }, + { + key_as_string: '2020-06-05T19:00:00.000Z', + key: 1591383600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T19:10:00.000Z', + key: 1591384200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T19:20:00.000Z', + key: 1591384800000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T19:30:00.000Z', + key: 1591385400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T19:40:00.000Z', + key: 1591386000000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T19:50:00.000Z', + key: 1591386600000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T20:00:00.000Z', + key: 1591387200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T20:10:00.000Z', + key: 1591387800000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T20:20:00.000Z', + key: 1591388400000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T20:30:00.000Z', + key: 1591389000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T20:40:00.000Z', + key: 1591389600000, + doc_count: 1, + }, + { + key_as_string: '2020-06-05T20:50:00.000Z', + key: 1591390200000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T21:00:00.000Z', + key: 1591390800000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T21:10:00.000Z', + key: 1591391400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T21:20:00.000Z', + key: 1591392000000, + doc_count: 6, + }, + { + key_as_string: '2020-06-05T21:30:00.000Z', + key: 1591392600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T21:40:00.000Z', + key: 1591393200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T21:50:00.000Z', + key: 1591393800000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T22:00:00.000Z', + key: 1591394400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T22:10:00.000Z', + key: 1591395000000, + doc_count: 2, + }, + { + key_as_string: '2020-06-05T22:20:00.000Z', + key: 1591395600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T22:30:00.000Z', + key: 1591396200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T22:40:00.000Z', + key: 1591396800000, + doc_count: 8, + }, + { + key_as_string: '2020-06-05T22:50:00.000Z', + key: 1591397400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-05T23:00:00.000Z', + key: 1591398000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T23:10:00.000Z', + key: 1591398600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T23:20:00.000Z', + key: 1591399200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-05T23:30:00.000Z', + key: 1591399800000, + doc_count: 7, + }, + { + key_as_string: '2020-06-05T23:40:00.000Z', + key: 1591400400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-05T23:50:00.000Z', + key: 1591401000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T00:00:00.000Z', + key: 1591401600000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T00:10:00.000Z', + key: 1591402200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T00:20:00.000Z', + key: 1591402800000, + doc_count: 1, + }, + { + key_as_string: '2020-06-06T00:30:00.000Z', + key: 1591403400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T00:40:00.000Z', + key: 1591404000000, + doc_count: 7, + }, + { + key_as_string: '2020-06-06T00:50:00.000Z', + key: 1591404600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T01:00:00.000Z', + key: 1591405200000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T01:10:00.000Z', + key: 1591405800000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T01:20:00.000Z', + key: 1591406400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T01:30:00.000Z', + key: 1591407000000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T01:40:00.000Z', + key: 1591407600000, + doc_count: 0, + }, + { + key_as_string: '2020-06-06T01:50:00.000Z', + key: 1591408200000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T02:00:00.000Z', + key: 1591408800000, + doc_count: 9, + }, + { + key_as_string: '2020-06-06T02:10:00.000Z', + key: 1591409400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T02:20:00.000Z', + key: 1591410000000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T02:30:00.000Z', + key: 1591410600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T02:40:00.000Z', + key: 1591411200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T02:50:00.000Z', + key: 1591411800000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T03:00:00.000Z', + key: 1591412400000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T03:10:00.000Z', + key: 1591413000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T03:20:00.000Z', + key: 1591413600000, + doc_count: 8, + }, + { + key_as_string: '2020-06-06T03:30:00.000Z', + key: 1591414200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T03:40:00.000Z', + key: 1591414800000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T03:50:00.000Z', + key: 1591415400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T04:00:00.000Z', + key: 1591416000000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T04:10:00.000Z', + key: 1591416600000, + doc_count: 7, + }, + { + key_as_string: '2020-06-06T04:20:00.000Z', + key: 1591417200000, + doc_count: 7, + }, + { + key_as_string: '2020-06-06T04:30:00.000Z', + key: 1591417800000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T04:40:00.000Z', + key: 1591418400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T04:50:00.000Z', + key: 1591419000000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T05:00:00.000Z', + key: 1591419600000, + doc_count: 7, + }, + { + key_as_string: '2020-06-06T05:10:00.000Z', + key: 1591420200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T05:20:00.000Z', + key: 1591420800000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T05:30:00.000Z', + key: 1591421400000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T05:40:00.000Z', + key: 1591422000000, + doc_count: 8, + }, + { + key_as_string: '2020-06-06T05:50:00.000Z', + key: 1591422600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T06:00:00.000Z', + key: 1591423200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T06:10:00.000Z', + key: 1591423800000, + doc_count: 1, + }, + { + key_as_string: '2020-06-06T06:20:00.000Z', + key: 1591424400000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T06:30:00.000Z', + key: 1591425000000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T06:40:00.000Z', + key: 1591425600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T06:50:00.000Z', + key: 1591426200000, + doc_count: 8, + }, + { + key_as_string: '2020-06-06T07:00:00.000Z', + key: 1591426800000, + doc_count: 8, + }, + { + key_as_string: '2020-06-06T07:10:00.000Z', + key: 1591427400000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T07:20:00.000Z', + key: 1591428000000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T07:30:00.000Z', + key: 1591428600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T07:40:00.000Z', + key: 1591429200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T07:50:00.000Z', + key: 1591429800000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T08:00:00.000Z', + key: 1591430400000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T08:10:00.000Z', + key: 1591431000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T08:20:00.000Z', + key: 1591431600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T08:30:00.000Z', + key: 1591432200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T08:40:00.000Z', + key: 1591432800000, + doc_count: 1, + }, + { + key_as_string: '2020-06-06T08:50:00.000Z', + key: 1591433400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T09:00:00.000Z', + key: 1591434000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T09:10:00.000Z', + key: 1591434600000, + doc_count: 9, + }, + { + key_as_string: '2020-06-06T09:20:00.000Z', + key: 1591435200000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T09:30:00.000Z', + key: 1591435800000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T09:40:00.000Z', + key: 1591436400000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T09:50:00.000Z', + key: 1591437000000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T10:00:00.000Z', + key: 1591437600000, + doc_count: 1, + }, + { + key_as_string: '2020-06-06T10:10:00.000Z', + key: 1591438200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T10:20:00.000Z', + key: 1591438800000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T10:30:00.000Z', + key: 1591439400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T10:40:00.000Z', + key: 1591440000000, + doc_count: 7, + }, + { + key_as_string: '2020-06-06T10:50:00.000Z', + key: 1591440600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T11:00:00.000Z', + key: 1591441200000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T11:10:00.000Z', + key: 1591441800000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T11:20:00.000Z', + key: 1591442400000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T11:30:00.000Z', + key: 1591443000000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T11:40:00.000Z', + key: 1591443600000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T11:50:00.000Z', + key: 1591444200000, + doc_count: 3, + }, + { + key_as_string: '2020-06-06T12:00:00.000Z', + key: 1591444800000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T12:10:00.000Z', + key: 1591445400000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T12:20:00.000Z', + key: 1591446000000, + doc_count: 6, + }, + { + key_as_string: '2020-06-06T12:30:00.000Z', + key: 1591446600000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T12:40:00.000Z', + key: 1591447200000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T12:50:00.000Z', + key: 1591447800000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T13:00:00.000Z', + key: 1591448400000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T13:10:00.000Z', + key: 1591449000000, + doc_count: 4, + }, + { + key_as_string: '2020-06-06T13:20:00.000Z', + key: 1591449600000, + doc_count: 5, + }, + { + key_as_string: '2020-06-06T13:30:00.000Z', + key: 1591450200000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T13:40:00.000Z', + key: 1591450800000, + doc_count: 2, + }, + { + key_as_string: '2020-06-06T13:50:00.000Z', + key: 1591451400000, + doc_count: 1, + }, + { + key_as_string: '2020-06-06T14:00:00.000Z', + key: 1591452000000, + doc_count: 2, + }, +]; + +export const apmData = [ + { + time: 1591365600000, + value: 32, + group: 'transaction', + }, + { + time: 1591365600000, + value: 5, + group: 'error', + }, + { + time: 1591366200000, + value: 43, + group: 'transaction', + }, + { + time: 1591366200000, + value: 4, + group: 'error', + }, + { + time: 1591366800000, + value: 22, + group: 'transaction', + }, + { + time: 1591366800000, + value: 1, + group: 'error', + }, + { + time: 1591367400000, + value: 29, + group: 'transaction', + }, + { + time: 1591367400000, + value: 5, + group: 'error', + }, + { + time: 1591368000000, + value: 39, + group: 'transaction', + }, + { + time: 1591368000000, + value: 8, + group: 'error', + }, + { + time: 1591368600000, + value: 36, + group: 'transaction', + }, + { + time: 1591368600000, + value: 4, + group: 'error', + }, + { + time: 1591369200000, + value: 50, + group: 'transaction', + }, + { + time: 1591369200000, + value: 3, + group: 'error', + }, + { + time: 1591369800000, + value: 31, + group: 'transaction', + }, + { + time: 1591369800000, + value: 6, + group: 'error', + }, + { + time: 1591370400000, + value: 39, + group: 'transaction', + }, + { + time: 1591370400000, + value: 9, + group: 'error', + }, + { + time: 1591371000000, + value: 26, + group: 'transaction', + }, + { + time: 1591371000000, + value: 1, + group: 'error', + }, + { + time: 1591371600000, + value: 45, + group: 'transaction', + }, + { + time: 1591371600000, + value: 7, + group: 'error', + }, + { + time: 1591372200000, + value: 27, + group: 'transaction', + }, + { + time: 1591372200000, + value: 4, + group: 'error', + }, + { + time: 1591372800000, + value: 37, + group: 'transaction', + }, + { + time: 1591372800000, + value: 8, + group: 'error', + }, + { + time: 1591373400000, + value: 55, + group: 'transaction', + }, + { + time: 1591373400000, + value: 6, + group: 'error', + }, + { + time: 1591374000000, + value: 31, + group: 'transaction', + }, + { + time: 1591374000000, + value: 3, + group: 'error', + }, + { + time: 1591374600000, + value: 26, + group: 'transaction', + }, + { + time: 1591374600000, + value: 5, + group: 'error', + }, + { + time: 1591375200000, + value: 57, + group: 'transaction', + }, + { + time: 1591375200000, + value: 7, + group: 'error', + }, + { + time: 1591375800000, + value: 25, + group: 'transaction', + }, + { + time: 1591375800000, + value: 5, + group: 'error', + }, + { + time: 1591376400000, + value: 28, + group: 'transaction', + }, + { + time: 1591376400000, + value: 6, + group: 'error', + }, + { + time: 1591377000000, + value: 40, + group: 'transaction', + }, + { + time: 1591377000000, + value: 4, + group: 'error', + }, + { + time: 1591377600000, + value: 33, + group: 'transaction', + }, + { + time: 1591377600000, + value: 4, + group: 'error', + }, + { + time: 1591378200000, + value: 33, + group: 'transaction', + }, + { + time: 1591378200000, + value: 7, + group: 'error', + }, + { + time: 1591378800000, + value: 31, + group: 'transaction', + }, + { + time: 1591378800000, + value: 5, + group: 'error', + }, + { + time: 1591379400000, + value: 32, + group: 'transaction', + }, + { + time: 1591379400000, + value: 7, + group: 'error', + }, + { + time: 1591380000000, + value: 34, + group: 'transaction', + }, + { + time: 1591380000000, + value: 6, + group: 'error', + }, + { + time: 1591380600000, + value: 31, + group: 'transaction', + }, + { + time: 1591380600000, + value: 3, + group: 'error', + }, + { + time: 1591381200000, + value: 16, + group: 'transaction', + }, + { + time: 1591381200000, + value: 4, + group: 'error', + }, + { + time: 1591381800000, + value: 34, + group: 'transaction', + }, + { + time: 1591381800000, + value: 5, + group: 'error', + }, + { + time: 1591382400000, + value: 33, + group: 'transaction', + }, + { + time: 1591382400000, + value: 4, + group: 'error', + }, + { + time: 1591383000000, + value: 35, + group: 'transaction', + }, + { + time: 1591383000000, + value: 8, + group: 'error', + }, + { + time: 1591383600000, + value: 47, + group: 'transaction', + }, + { + time: 1591383600000, + value: 5, + group: 'error', + }, + { + time: 1591384200000, + value: 44, + group: 'transaction', + }, + { + time: 1591384200000, + value: 5, + group: 'error', + }, + { + time: 1591384800000, + value: 21, + group: 'transaction', + }, + { + time: 1591384800000, + value: 4, + group: 'error', + }, + { + time: 1591385400000, + value: 25, + group: 'transaction', + }, + { + time: 1591385400000, + value: 5, + group: 'error', + }, + { + time: 1591386000000, + value: 34, + group: 'transaction', + }, + { + time: 1591386000000, + value: 5, + group: 'error', + }, + { + time: 1591386600000, + value: 37, + group: 'transaction', + }, + { + time: 1591386600000, + value: 7, + group: 'error', + }, + { + time: 1591387200000, + value: 38, + group: 'transaction', + }, + { + time: 1591387200000, + value: 5, + group: 'error', + }, + { + time: 1591387800000, + value: 28, + group: 'transaction', + }, + { + time: 1591387800000, + value: 6, + group: 'error', + }, + { + time: 1591388400000, + value: 32, + group: 'transaction', + }, + { + time: 1591388400000, + value: 6, + group: 'error', + }, + { + time: 1591389000000, + value: 37, + group: 'transaction', + }, + { + time: 1591389000000, + value: 4, + group: 'error', + }, + { + time: 1591389600000, + value: 25, + group: 'transaction', + }, + { + time: 1591389600000, + value: 1, + group: 'error', + }, + { + time: 1591390200000, + value: 33, + group: 'transaction', + }, + { + time: 1591390200000, + value: 6, + group: 'error', + }, + { + time: 1591390800000, + value: 34, + group: 'transaction', + }, + { + time: 1591390800000, + value: 7, + group: 'error', + }, + { + time: 1591391400000, + value: 30, + group: 'transaction', + }, + { + time: 1591391400000, + value: 5, + group: 'error', + }, + { + time: 1591392000000, + value: 45, + group: 'transaction', + }, + { + time: 1591392000000, + value: 6, + group: 'error', + }, + { + time: 1591392600000, + value: 42, + group: 'transaction', + }, + { + time: 1591392600000, + value: 5, + group: 'error', + }, + { + time: 1591393200000, + value: 23, + group: 'transaction', + }, + { + time: 1591393200000, + value: 3, + group: 'error', + }, + { + time: 1591393800000, + value: 33, + group: 'transaction', + }, + { + time: 1591393800000, + value: 3, + group: 'error', + }, + { + time: 1591394400000, + value: 38, + group: 'transaction', + }, + { + time: 1591394400000, + value: 4, + group: 'error', + }, + { + time: 1591395000000, + value: 30, + group: 'transaction', + }, + { + time: 1591395000000, + value: 2, + group: 'error', + }, + { + time: 1591395600000, + value: 25, + group: 'transaction', + }, + { + time: 1591395600000, + value: 4, + group: 'error', + }, + { + time: 1591396200000, + value: 33, + group: 'transaction', + }, + { + time: 1591396200000, + value: 3, + group: 'error', + }, + { + time: 1591396800000, + value: 37, + group: 'transaction', + }, + { + time: 1591396800000, + value: 8, + group: 'error', + }, + { + time: 1591397400000, + value: 43, + group: 'transaction', + }, + { + time: 1591397400000, + value: 5, + group: 'error', + }, + { + time: 1591398000000, + value: 30, + group: 'transaction', + }, + { + time: 1591398000000, + value: 4, + group: 'error', + }, + { + time: 1591398600000, + value: 36, + group: 'transaction', + }, + { + time: 1591398600000, + value: 4, + group: 'error', + }, + { + time: 1591399200000, + value: 28, + group: 'transaction', + }, + { + time: 1591399200000, + value: 3, + group: 'error', + }, + { + time: 1591399800000, + value: 39, + group: 'transaction', + }, + { + time: 1591399800000, + value: 7, + group: 'error', + }, + { + time: 1591400400000, + value: 27, + group: 'transaction', + }, + { + time: 1591400400000, + value: 4, + group: 'error', + }, + { + time: 1591401000000, + value: 41, + group: 'transaction', + }, + { + time: 1591401000000, + value: 4, + group: 'error', + }, + { + time: 1591401600000, + value: 25, + group: 'transaction', + }, + { + time: 1591401600000, + value: 3, + group: 'error', + }, + { + time: 1591402200000, + value: 31, + group: 'transaction', + }, + { + time: 1591402200000, + value: 3, + group: 'error', + }, + { + time: 1591402800000, + value: 28, + group: 'transaction', + }, + { + time: 1591402800000, + value: 1, + group: 'error', + }, + { + time: 1591403400000, + value: 29, + group: 'transaction', + }, + { + time: 1591403400000, + value: 5, + group: 'error', + }, + { + time: 1591404000000, + value: 49, + group: 'transaction', + }, + { + time: 1591404000000, + value: 7, + group: 'error', + }, + { + time: 1591404600000, + value: 24, + group: 'transaction', + }, + { + time: 1591404600000, + value: 5, + group: 'error', + }, + { + time: 1591405200000, + value: 41, + group: 'transaction', + }, + { + time: 1591405200000, + value: 6, + group: 'error', + }, + { + time: 1591405800000, + value: 30, + group: 'transaction', + }, + { + time: 1591405800000, + value: 3, + group: 'error', + }, + { + time: 1591406400000, + value: 36, + group: 'transaction', + }, + { + time: 1591406400000, + value: 5, + group: 'error', + }, + { + time: 1591407000000, + value: 39, + group: 'transaction', + }, + { + time: 1591407000000, + value: 5, + group: 'error', + }, + { + time: 1591407600000, + value: 23, + group: 'transaction', + }, + { + time: 1591407600000, + value: 0, + group: 'error', + }, + { + time: 1591408200000, + value: 40, + group: 'transaction', + }, + { + time: 1591408200000, + value: 4, + group: 'error', + }, + { + time: 1591408800000, + value: 34, + group: 'transaction', + }, + { + time: 1591408800000, + value: 9, + group: 'error', + }, + { + time: 1591409400000, + value: 28, + group: 'transaction', + }, + { + time: 1591409400000, + value: 5, + group: 'error', + }, + { + time: 1591410000000, + value: 33, + group: 'transaction', + }, + { + time: 1591410000000, + value: 6, + group: 'error', + }, + { + time: 1591410600000, + value: 31, + group: 'transaction', + }, + { + time: 1591410600000, + value: 4, + group: 'error', + }, + { + time: 1591411200000, + value: 39, + group: 'transaction', + }, + { + time: 1591411200000, + value: 5, + group: 'error', + }, + { + time: 1591411800000, + value: 33, + group: 'transaction', + }, + { + time: 1591411800000, + value: 5, + group: 'error', + }, + { + time: 1591412400000, + value: 35, + group: 'transaction', + }, + { + time: 1591412400000, + value: 2, + group: 'error', + }, + { + time: 1591413000000, + value: 31, + group: 'transaction', + }, + { + time: 1591413000000, + value: 4, + group: 'error', + }, + { + time: 1591413600000, + value: 35, + group: 'transaction', + }, + { + time: 1591413600000, + value: 8, + group: 'error', + }, + { + time: 1591414200000, + value: 37, + group: 'transaction', + }, + { + time: 1591414200000, + value: 5, + group: 'error', + }, + { + time: 1591414800000, + value: 26, + group: 'transaction', + }, + { + time: 1591414800000, + value: 4, + group: 'error', + }, + { + time: 1591415400000, + value: 27, + group: 'transaction', + }, + { + time: 1591415400000, + value: 4, + group: 'error', + }, + { + time: 1591416000000, + value: 26, + group: 'transaction', + }, + { + time: 1591416000000, + value: 3, + group: 'error', + }, + { + time: 1591416600000, + value: 34, + group: 'transaction', + }, + { + time: 1591416600000, + value: 7, + group: 'error', + }, + { + time: 1591417200000, + value: 33, + group: 'transaction', + }, + { + time: 1591417200000, + value: 7, + group: 'error', + }, + { + time: 1591417800000, + value: 38, + group: 'transaction', + }, + { + time: 1591417800000, + value: 6, + group: 'error', + }, + { + time: 1591418400000, + value: 34, + group: 'transaction', + }, + { + time: 1591418400000, + value: 5, + group: 'error', + }, + { + time: 1591419000000, + value: 37, + group: 'transaction', + }, + { + time: 1591419000000, + value: 5, + group: 'error', + }, + { + time: 1591419600000, + value: 24, + group: 'transaction', + }, + { + time: 1591419600000, + value: 7, + group: 'error', + }, + { + time: 1591420200000, + value: 25, + group: 'transaction', + }, + { + time: 1591420200000, + value: 5, + group: 'error', + }, + { + time: 1591420800000, + value: 20, + group: 'transaction', + }, + { + time: 1591420800000, + value: 2, + group: 'error', + }, + { + time: 1591421400000, + value: 35, + group: 'transaction', + }, + { + time: 1591421400000, + value: 3, + group: 'error', + }, + { + time: 1591422000000, + value: 41, + group: 'transaction', + }, + { + time: 1591422000000, + value: 8, + group: 'error', + }, + { + time: 1591422600000, + value: 40, + group: 'transaction', + }, + { + time: 1591422600000, + value: 5, + group: 'error', + }, + { + time: 1591423200000, + value: 33, + group: 'transaction', + }, + { + time: 1591423200000, + value: 5, + group: 'error', + }, + { + time: 1591423800000, + value: 24, + group: 'transaction', + }, + { + time: 1591423800000, + value: 1, + group: 'error', + }, + { + time: 1591424400000, + value: 44, + group: 'transaction', + }, + { + time: 1591424400000, + value: 3, + group: 'error', + }, + { + time: 1591425000000, + value: 24, + group: 'transaction', + }, + { + time: 1591425000000, + value: 5, + group: 'error', + }, + { + time: 1591425600000, + value: 32, + group: 'transaction', + }, + { + time: 1591425600000, + value: 4, + group: 'error', + }, + { + time: 1591426200000, + value: 37, + group: 'transaction', + }, + { + time: 1591426200000, + value: 8, + group: 'error', + }, + { + time: 1591426800000, + value: 34, + group: 'transaction', + }, + { + time: 1591426800000, + value: 8, + group: 'error', + }, + { + time: 1591427400000, + value: 28, + group: 'transaction', + }, + { + time: 1591427400000, + value: 5, + group: 'error', + }, + { + time: 1591428000000, + value: 26, + group: 'transaction', + }, + { + time: 1591428000000, + value: 3, + group: 'error', + }, + { + time: 1591428600000, + value: 37, + group: 'transaction', + }, + { + time: 1591428600000, + value: 4, + group: 'error', + }, + { + time: 1591429200000, + value: 36, + group: 'transaction', + }, + { + time: 1591429200000, + value: 3, + group: 'error', + }, + { + time: 1591429800000, + value: 37, + group: 'transaction', + }, + { + time: 1591429800000, + value: 5, + group: 'error', + }, + { + time: 1591430400000, + value: 23, + group: 'transaction', + }, + { + time: 1591430400000, + value: 3, + group: 'error', + }, + { + time: 1591431000000, + value: 47, + group: 'transaction', + }, + { + time: 1591431000000, + value: 4, + group: 'error', + }, + { + time: 1591431600000, + value: 41, + group: 'transaction', + }, + { + time: 1591431600000, + value: 4, + group: 'error', + }, + { + time: 1591432200000, + value: 24, + group: 'transaction', + }, + { + time: 1591432200000, + value: 5, + group: 'error', + }, + { + time: 1591432800000, + value: 34, + group: 'transaction', + }, + { + time: 1591432800000, + value: 1, + group: 'error', + }, + { + time: 1591433400000, + value: 27, + group: 'transaction', + }, + { + time: 1591433400000, + value: 4, + group: 'error', + }, + { + time: 1591434000000, + value: 34, + group: 'transaction', + }, + { + time: 1591434000000, + value: 4, + group: 'error', + }, + { + time: 1591434600000, + value: 44, + group: 'transaction', + }, + { + time: 1591434600000, + value: 9, + group: 'error', + }, + { + time: 1591435200000, + value: 20, + group: 'transaction', + }, + { + time: 1591435200000, + value: 4, + group: 'error', + }, + { + time: 1591435800000, + value: 34, + group: 'transaction', + }, + { + time: 1591435800000, + value: 4, + group: 'error', + }, + { + time: 1591436400000, + value: 29, + group: 'transaction', + }, + { + time: 1591436400000, + value: 3, + group: 'error', + }, + { + time: 1591437000000, + value: 28, + group: 'transaction', + }, + { + time: 1591437000000, + value: 6, + group: 'error', + }, + { + time: 1591437600000, + value: 36, + group: 'transaction', + }, + { + time: 1591437600000, + value: 1, + group: 'error', + }, + { + time: 1591438200000, + value: 34, + group: 'transaction', + }, + { + time: 1591438200000, + value: 5, + group: 'error', + }, + { + time: 1591438800000, + value: 26, + group: 'transaction', + }, + { + time: 1591438800000, + value: 4, + group: 'error', + }, + { + time: 1591439400000, + value: 29, + group: 'transaction', + }, + { + time: 1591439400000, + value: 4, + group: 'error', + }, + { + time: 1591440000000, + value: 45, + group: 'transaction', + }, + { + time: 1591440000000, + value: 7, + group: 'error', + }, + { + time: 1591440600000, + value: 34, + group: 'transaction', + }, + { + time: 1591440600000, + value: 5, + group: 'error', + }, + { + time: 1591441200000, + value: 25, + group: 'transaction', + }, + { + time: 1591441200000, + value: 6, + group: 'error', + }, + { + time: 1591441800000, + value: 34, + group: 'transaction', + }, + { + time: 1591441800000, + value: 6, + group: 'error', + }, + { + time: 1591442400000, + value: 28, + group: 'transaction', + }, + { + time: 1591442400000, + value: 4, + group: 'error', + }, + { + time: 1591443000000, + value: 34, + group: 'transaction', + }, + { + time: 1591443000000, + value: 5, + group: 'error', + }, + { + time: 1591443600000, + value: 31, + group: 'transaction', + }, + { + time: 1591443600000, + value: 3, + group: 'error', + }, + { + time: 1591444200000, + value: 24, + group: 'transaction', + }, + { + time: 1591444200000, + value: 3, + group: 'error', + }, + { + time: 1591444800000, + value: 34, + group: 'transaction', + }, + { + time: 1591444800000, + value: 4, + group: 'error', + }, + { + time: 1591445400000, + value: 21, + group: 'transaction', + }, + { + time: 1591445400000, + value: 2, + group: 'error', + }, + { + time: 1591446000000, + value: 40, + group: 'transaction', + }, + { + time: 1591446000000, + value: 6, + group: 'error', + }, + { + time: 1591446600000, + value: 37, + group: 'transaction', + }, + { + time: 1591446600000, + value: 4, + group: 'error', + }, + { + time: 1591447200000, + value: 31, + group: 'transaction', + }, + { + time: 1591447200000, + value: 5, + group: 'error', + }, + { + time: 1591447800000, + value: 21, + group: 'transaction', + }, + { + time: 1591447800000, + value: 2, + group: 'error', + }, + { + time: 1591448400000, + value: 24, + group: 'transaction', + }, + { + time: 1591448400000, + value: 2, + group: 'error', + }, + { + time: 1591449000000, + value: 30, + group: 'transaction', + }, + { + time: 1591449000000, + value: 4, + group: 'error', + }, + { + time: 1591449600000, + value: 22, + group: 'transaction', + }, + { + time: 1591449600000, + value: 5, + group: 'error', + }, + { + time: 1591450200000, + value: 27, + group: 'transaction', + }, + { + time: 1591450200000, + value: 2, + group: 'error', + }, + { + time: 1591450800000, + value: 30, + group: 'transaction', + }, + { + time: 1591450800000, + value: 2, + group: 'error', + }, + { + time: 1591451400000, + value: 22, + group: 'transaction', + }, + { + time: 1591451400000, + value: 1, + group: 'error', + }, + { + time: 1591452000000, + value: 9, + group: 'transaction', + }, + { + time: 1591452000000, + value: 2, + group: 'error', + }, +]; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index c6bc6cf5676e5..e7d2f78b1fe09 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -3,20 +3,71 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { + Axis, + BarSeries, + Chart, + Position, + ScaleType, + timeFormatter, + niceTimeFormatByDay, + Settings, + niceTimeFormatter, + DARK_THEME, + LIGHT_THEME, + LineSeries, + AreaSeries, +} from '@elastic/charts'; +import { + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiSwitch, +} from '@elastic/eui'; import React, { useContext, useState } from 'react'; import { ThemeContext } from 'styled-components'; -import { EuiFlexGrid } from '@elastic/eui'; -import { EuiSpacer } from '@elastic/eui'; -import { EuiSwitch } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { groupBy } from 'lodash'; import { ChartContainer } from '../../components/chart/container'; import { WithHeaderLayout } from '../../components/layout/with_header'; -import { ContinueJourney } from './continue_journey'; import { appsSection } from '../home/section'; +import { ContinueJourney } from './continue_journey'; +import { series } from './logs.data'; +import { barData } from './uptime.data'; +import { apmData } from './apm.data'; export const Overview = () => { const theme = useContext(ThemeContext); - const [withAlert, setWithAlert] = useState(true); + const [withAlert, setWithAlert] = useState(false); + + // const start = series[0].time; + // const end = series[series.length - 1].time; + // const formatter = niceTimeFormatter([start, end]); + + // const startUptime = barData[0].x; + // const endUptime = barData[barData.length - 1].x; + // const formatterUptime = niceTimeFormatter([startUptime, endUptime]); + + const { transaction: transactions, error: errors } = groupBy(apmData, (d) => d.group); + + const startAPM = transactions[0].time; + const endAPM = transactions[transactions.length - 1].time; + const formatterAPM = niceTimeFormatter([startAPM, endAPM]); + + const barSeriesColorAccessor = ({ specId, yAccessor, splitAccessors }: any) => { + if (splitAccessors.get('group') === 'error') { + return 'lightgray'; + } + return 'red'; + }; + const barSeriesColorAccessor2 = ({ specId, yAccessor, splitAccessors }: any) => { + if (splitAccessors.get('group') === 'error') { + return '#CA8EAE'; + } + return '#9170B8'; + }; return ( <WithHeaderLayout @@ -34,22 +85,176 @@ export const Overview = () => { <EuiFlexGroup direction="column"> <EuiFlexItem> <ChartContainer title="Logs"> - <EuiPanel>chart goes here</EuiPanel> + <Chart> + <Settings + onBrushEnd={({ x }) => { + console.log('#### Logs', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + onElementOver={(x) => { + console.log('##########3', x[0][0].x); + }} + /> + <Axis + id="bottom" + position={Position.Bottom} + showOverlappingTicks={false} + showOverlappingLabels={false} + tickFormat={formatterAPM} + /> + <Axis + showGridLines + id="left2" + position={Position.Left} + tickFormat={(d: any) => numeral(d).format('0a')} + /> + + <BarSeries + id="averageValues" + xScaleType="time" + yScaleType="linear" + xAccessor={'time'} + yAccessors={['value']} + splitSeriesAccessors={['group']} + stackAccessors={['time']} + data={apmData} + /> + </Chart> </ChartContainer> </EuiFlexItem> <EuiFlexItem> <ChartContainer title="Metrics"> - <EuiPanel>chart goes here</EuiPanel> + <Chart> + <Settings + onBrushEnd={({ x }) => { + console.log('#### Metrics', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + /> + <Axis + id="bottom" + position={Position.Bottom} + showOverlappingTicks={false} + showOverlappingLabels={false} + tickFormat={formatterAPM} + /> + <Axis + showGridLines + id="left2" + position={Position.Left} + tickFormat={(d: any) => numeral(d).format('0a')} + /> + <AreaSeries + id="averageValues" + xScaleType="time" + yScaleType="linear" + xAccessor={'time'} + yAccessors={['value']} + splitSeriesAccessors={['group']} + stackAccessors={['time']} + data={apmData} + color={barSeriesColorAccessor2} + /> + </Chart> </ChartContainer> </EuiFlexItem> <EuiFlexItem> <ChartContainer title="APM"> - <EuiPanel>chart goes here</EuiPanel> + <Chart> + <Settings + onBrushEnd={({ x }) => { + console.log('#### APM', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend={true} + legendPosition="bottom" + /> + <BarSeries + id="transactions" + name="Transactions" + data={transactions} + xScaleType="time" + xAccessor={'time'} + yAccessors={['value']} + color="blue" + groupId="transactions" + /> + <LineSeries + id="errors" + name="Errors" + data={errors} + xScaleType="time" + xAccessor={'time'} + yAccessors={['value']} + color="gold" + groupId="errors" + /> + <Axis + id="bottom-axis" + position="bottom" + tickFormat={formatterAPM} + showGridLines + /> + <Axis + id="right" + position={Position.Right} + tickFormat={(d) => `${Number(d).toFixed(0)} %`} + groupId="errors" + /> + <Axis + id="left-axis" + position="left" + showGridLines + groupId="transactions" + tickFormat={(d) => numeral(d).format('0a')} + /> + </Chart> </ChartContainer> </EuiFlexItem> <EuiFlexItem> <ChartContainer title="Uptime"> - <EuiPanel>chart goes here</EuiPanel> + <Chart> + <Settings + onBrushEnd={({ x }) => { + console.log('#### Uptime', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + /> + <Axis + id="bottom" + position={Position.Bottom} + showOverlappingTicks={false} + showOverlappingLabels={false} + tickFormat={formatterAPM} + /> + <Axis + showGridLines + id="left2" + position={Position.Left} + tickFormat={(d: any) => numeral(d).format('0a')} + /> + + <BarSeries + id="averageValues" + xScaleType="time" + yScaleType="linear" + xAccessor={'time'} + yAccessors={['value']} + splitSeriesAccessors={['group']} + stackAccessors={['time']} + data={apmData} + color={barSeriesColorAccessor} + /> + </Chart> </ChartContainer> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/observability/public/pages/overview/logs.data.ts b/x-pack/plugins/observability/public/pages/overview/logs.data.ts new file mode 100644 index 0000000000000..3102a309d29aa --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/logs.data.ts @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const series = [ + { + group: 'unknown', + time: 1588942800000, + value: 7382.380952380952, + }, + { + group: 'kibana.log', + time: 1588942800000, + value: 358.3333333333333, + }, + { + group: 'nginx.access', + time: 1588942800000, + value: 594.3174603174604, + }, + { + group: 'nginx.error', + time: 1588942800000, + value: 134.79365079365078, + }, + { + group: 'postgresql.log', + time: 1588942800000, + value: 0.1935483870967742, + }, + { + group: 'redis.log', + time: 1588942800000, + value: 11.714285714285714, + }, + { + group: 'redis.slowlog', + time: 1588942800000, + value: 11.714285714285714, + }, + { + group: 'unknown', + time: 1589078700000, + value: 7147.549668874172, + }, + { + group: 'kibana.log', + time: 1589078700000, + value: 321.26490066225165, + }, + { + group: 'nginx.access', + time: 1589078700000, + value: 637.9072847682119, + }, + { + group: 'nginx.error', + time: 1589078700000, + value: 135.9205298013245, + }, + { + group: 'postgresql.log', + time: 1589078700000, + value: 0.4370860927152318, + }, + { + group: 'redis.log', + time: 1589078700000, + value: 5.880794701986755, + }, + { + group: 'redis.slowlog', + time: 1589078700000, + value: 5.880794701986755, + }, + { + group: 'unknown', + time: 1589214600000, + value: 7137.556291390729, + }, + { + group: 'kibana.log', + time: 1589214600000, + value: 178.26490066225165, + }, + { + group: 'nginx.access', + time: 1589214600000, + value: 798.9867549668874, + }, + { + group: 'nginx.error', + time: 1589214600000, + value: 123.67549668874172, + }, + { + group: 'postgresql.log', + time: 1589214600000, + value: 0.3973509933774834, + }, + { + group: 'redis.log', + time: 1589214600000, + value: 0.4304635761589404, + }, + { + group: 'redis.slowlog', + time: 1589214600000, + value: 0.4304635761589404, + }, + { + group: 'unknown', + time: 1589350500000, + value: 7510.6688741721855, + }, + { + group: 'kibana.log', + time: 1589350500000, + value: 152.63576158940398, + }, + { + group: 'nginx.access', + time: 1589350500000, + value: 823.3973509933775, + }, + { + group: 'nginx.error', + time: 1589350500000, + value: 126.11920529801324, + }, + { + group: 'postgresql.log', + time: 1589350500000, + value: 0.33112582781456956, + }, + { + group: 'redis.log', + time: 1589350500000, + value: 0.4370860927152318, + }, + { + group: 'redis.slowlog', + time: 1589350500000, + value: 0.4370860927152318, + }, + { + group: 'unknown', + time: 1589486400000, + value: 9013.788079470198, + }, + { + group: 'kibana.log', + time: 1589486400000, + value: 3.2781456953642385, + }, + { + group: 'nginx.access', + time: 1589486400000, + value: 883.3112582781457, + }, + { + group: 'nginx.error', + time: 1589486400000, + value: 144.1523178807947, + }, + { + group: 'postgresql.log', + time: 1589486400000, + value: 0.2980132450331126, + }, + { + group: 'redis.log', + time: 1589486400000, + value: 0.41721854304635764, + }, + { + group: 'redis.slowlog', + time: 1589486400000, + value: 0.41721854304635764, + }, + { + group: 'unknown', + time: 1589622300000, + value: 9022.754966887418, + }, + { + group: 'kibana.log', + time: 1589622300000, + value: 0.08609271523178808, + }, + { + group: 'nginx.access', + time: 1589622300000, + value: 840.6423841059602, + }, + { + group: 'nginx.error', + time: 1589622300000, + value: 144.74834437086093, + }, + { + group: 'postgresql.log', + time: 1589622300000, + value: 0.3973509933774834, + }, + { + group: 'redis.log', + time: 1589622300000, + value: 0.423841059602649, + }, + { + group: 'redis.slowlog', + time: 1589622300000, + value: 0.423841059602649, + }, + { + group: 'unknown', + time: 1589758200000, + value: 9042.53642384106, + }, + { + group: 'kibana.log', + time: 1589758200000, + value: 3.7218543046357615, + }, + { + group: 'nginx.access', + time: 1589758200000, + value: 863.8079470198676, + }, + { + group: 'nginx.error', + time: 1589758200000, + value: 144.40397350993376, + }, + { + group: 'postgresql.log', + time: 1589758200000, + value: 0.4768211920529801, + }, + { + group: 'redis.log', + time: 1589758200000, + value: 0.271523178807947, + }, + { + group: 'redis.slowlog', + time: 1589758200000, + value: 0.271523178807947, + }, + { + group: 'unknown', + time: 1589894100000, + value: 8690.76821192053, + }, + { + group: 'haproxy.log', + time: 1589894100000, + value: 0, + }, + { + group: 'kibana.log', + time: 1589894100000, + value: 0.8741721854304636, + }, + { + group: 'nginx.access', + time: 1589894100000, + value: 86.54966887417218, + }, + { + group: 'nginx.error', + time: 1589894100000, + value: 12.675496688741722, + }, + { + group: 'postgresql.log', + time: 1589894100000, + value: 0.09271523178807947, + }, + { + group: 'redis.log', + time: 1589894100000, + value: 0.3708609271523179, + }, + { + group: 'redis.slowlog', + time: 1589894100000, + value: 0.3973509933774834, + }, + { + group: 'unknown', + time: 1590030000000, + value: 8829.204545454546, + }, + { + group: 'haproxy.log', + time: 1590030000000, + value: 0, + }, + { + group: 'kibana.log', + time: 1590030000000, + value: 0, + }, + { + group: 'nginx.access', + time: 1590030000000, + value: 0, + }, + { + group: 'nginx.error', + time: 1590030000000, + value: 0, + }, + { + group: 'postgresql.log', + time: 1590030000000, + value: 0, + }, + { + group: 'redis.log', + time: 1590030000000, + value: 0, + }, + { + group: 'redis.slowlog', + time: 1590030000000, + value: 0, + }, +]; diff --git a/x-pack/plugins/observability/public/pages/overview/uptime.data.ts b/x-pack/plugins/observability/public/pages/overview/uptime.data.ts new file mode 100644 index 0000000000000..e8127a66c4165 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/uptime.data.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const barData = [ + { + time: 1591439940000, + value: 2, + group: 'Down', + }, + { + time: 1591439940000, + value: 12, + group: 'Up', + }, + { + time: 1591440000000, + value: 6, + group: 'Down', + }, + { + time: 1591440000000, + value: 30, + group: 'Up', + }, + { + time: 1591440060000, + value: 6, + group: 'Down', + }, + { + time: 1591440060000, + value: 30, + group: 'Up', + }, + { + time: 1591440120000, + value: 6, + group: 'Down', + }, + { + time: 1591440120000, + value: 30, + group: 'Up', + }, + { + time: 1591440180000, + value: 15, + group: 'Down', + }, + { + time: 1591440180000, + value: 75, + group: 'Up', + }, + { + time: 1591440240000, + value: 6, + group: 'Down', + }, + { + time: 1591440240000, + value: 30, + group: 'Up', + }, + { + time: 1591440300000, + value: 6, + group: 'Down', + }, + { + time: 1591440300000, + value: 30, + group: 'Up', + }, + { + time: 1591440360000, + value: 6, + group: 'Down', + }, + { + time: 1591440360000, + value: 30, + group: 'Up', + }, + { + time: 1591440420000, + value: 6, + group: 'Down', + }, + { + time: 1591440420000, + value: 30, + group: 'Up', + }, + { + time: 1591440480000, + value: 15, + group: 'Down', + }, + { + time: 1591440480000, + value: 75, + group: 'Up', + }, + { + time: 1591440540000, + value: 6, + group: 'Down', + }, + { + time: 1591440540000, + value: 30, + group: 'Up', + }, + { + time: 1591440600000, + value: 6, + group: 'Down', + }, + { + time: 1591440600000, + value: 30, + group: 'Up', + }, + { + time: 1591440660000, + value: 6, + group: 'Down', + }, + { + time: 1591440660000, + value: 30, + group: 'Up', + }, + { + time: 1591440720000, + value: 6, + group: 'Down', + }, + { + time: 1591440720000, + value: 30, + group: 'Up', + }, + { + time: 1591440780000, + value: 15, + group: 'Down', + }, + { + time: 1591440780000, + value: 75, + group: 'Up', + }, + { + time: 1591440840000, + value: 2, + group: 'Down', + }, + { + time: 1591440840000, + value: 15, + group: 'Up', + }, +]; From 13c89b9230ec9ef46ce8bf71a6de2378a019ee8c Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Tue, 9 Jun 2020 08:01:23 +0200 Subject: [PATCH 27/97] mocking data --- .../public/pages/overview/apm.data.ts | 1456 ----------------- .../public/pages/overview/index.tsx | 3 - .../public/typings/chart/index.ts | 13 + 3 files changed, 13 insertions(+), 1459 deletions(-) create mode 100644 x-pack/plugins/observability/public/typings/chart/index.ts diff --git a/x-pack/plugins/observability/public/pages/overview/apm.data.ts b/x-pack/plugins/observability/public/pages/overview/apm.data.ts index ea364ba7f4189..576e9dd1867e2 100644 --- a/x-pack/plugins/observability/public/pages/overview/apm.data.ts +++ b/x-pack/plugins/observability/public/pages/overview/apm.data.ts @@ -4,1462 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const transactions = [ - { - key_as_string: '2020-06-05T14:00:00.000Z', - key: 1591365600000, - doc_count: 32, - }, - { - key_as_string: '2020-06-05T14:10:00.000Z', - key: 1591366200000, - doc_count: 43, - }, - { - key_as_string: '2020-06-05T14:20:00.000Z', - key: 1591366800000, - doc_count: 22, - }, - { - key_as_string: '2020-06-05T14:30:00.000Z', - key: 1591367400000, - doc_count: 29, - }, - { - key_as_string: '2020-06-05T14:40:00.000Z', - key: 1591368000000, - doc_count: 39, - }, - { - key_as_string: '2020-06-05T14:50:00.000Z', - key: 1591368600000, - doc_count: 36, - }, - { - key_as_string: '2020-06-05T15:00:00.000Z', - key: 1591369200000, - doc_count: 50, - }, - { - key_as_string: '2020-06-05T15:10:00.000Z', - key: 1591369800000, - doc_count: 31, - }, - { - key_as_string: '2020-06-05T15:20:00.000Z', - key: 1591370400000, - doc_count: 39, - }, - { - key_as_string: '2020-06-05T15:30:00.000Z', - key: 1591371000000, - doc_count: 26, - }, - { - key_as_string: '2020-06-05T15:40:00.000Z', - key: 1591371600000, - doc_count: 45, - }, - { - key_as_string: '2020-06-05T15:50:00.000Z', - key: 1591372200000, - doc_count: 27, - }, - { - key_as_string: '2020-06-05T16:00:00.000Z', - key: 1591372800000, - doc_count: 37, - }, - { - key_as_string: '2020-06-05T16:10:00.000Z', - key: 1591373400000, - doc_count: 55, - }, - { - key_as_string: '2020-06-05T16:20:00.000Z', - key: 1591374000000, - doc_count: 31, - }, - { - key_as_string: '2020-06-05T16:30:00.000Z', - key: 1591374600000, - doc_count: 26, - }, - { - key_as_string: '2020-06-05T16:40:00.000Z', - key: 1591375200000, - doc_count: 57, - }, - { - key_as_string: '2020-06-05T16:50:00.000Z', - key: 1591375800000, - doc_count: 25, - }, - { - key_as_string: '2020-06-05T17:00:00.000Z', - key: 1591376400000, - doc_count: 28, - }, - { - key_as_string: '2020-06-05T17:10:00.000Z', - key: 1591377000000, - doc_count: 40, - }, - { - key_as_string: '2020-06-05T17:20:00.000Z', - key: 1591377600000, - doc_count: 33, - }, - { - key_as_string: '2020-06-05T17:30:00.000Z', - key: 1591378200000, - doc_count: 33, - }, - { - key_as_string: '2020-06-05T17:40:00.000Z', - key: 1591378800000, - doc_count: 31, - }, - { - key_as_string: '2020-06-05T17:50:00.000Z', - key: 1591379400000, - doc_count: 32, - }, - { - key_as_string: '2020-06-05T18:00:00.000Z', - key: 1591380000000, - doc_count: 34, - }, - { - key_as_string: '2020-06-05T18:10:00.000Z', - key: 1591380600000, - doc_count: 31, - }, - { - key_as_string: '2020-06-05T18:20:00.000Z', - key: 1591381200000, - doc_count: 16, - }, - { - key_as_string: '2020-06-05T18:30:00.000Z', - key: 1591381800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-05T18:40:00.000Z', - key: 1591382400000, - doc_count: 33, - }, - { - key_as_string: '2020-06-05T18:50:00.000Z', - key: 1591383000000, - doc_count: 35, - }, - { - key_as_string: '2020-06-05T19:00:00.000Z', - key: 1591383600000, - doc_count: 47, - }, - { - key_as_string: '2020-06-05T19:10:00.000Z', - key: 1591384200000, - doc_count: 44, - }, - { - key_as_string: '2020-06-05T19:20:00.000Z', - key: 1591384800000, - doc_count: 21, - }, - { - key_as_string: '2020-06-05T19:30:00.000Z', - key: 1591385400000, - doc_count: 25, - }, - { - key_as_string: '2020-06-05T19:40:00.000Z', - key: 1591386000000, - doc_count: 34, - }, - { - key_as_string: '2020-06-05T19:50:00.000Z', - key: 1591386600000, - doc_count: 37, - }, - { - key_as_string: '2020-06-05T20:00:00.000Z', - key: 1591387200000, - doc_count: 38, - }, - { - key_as_string: '2020-06-05T20:10:00.000Z', - key: 1591387800000, - doc_count: 28, - }, - { - key_as_string: '2020-06-05T20:20:00.000Z', - key: 1591388400000, - doc_count: 32, - }, - { - key_as_string: '2020-06-05T20:30:00.000Z', - key: 1591389000000, - doc_count: 37, - }, - { - key_as_string: '2020-06-05T20:40:00.000Z', - key: 1591389600000, - doc_count: 25, - }, - { - key_as_string: '2020-06-05T20:50:00.000Z', - key: 1591390200000, - doc_count: 33, - }, - { - key_as_string: '2020-06-05T21:00:00.000Z', - key: 1591390800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-05T21:10:00.000Z', - key: 1591391400000, - doc_count: 30, - }, - { - key_as_string: '2020-06-05T21:20:00.000Z', - key: 1591392000000, - doc_count: 45, - }, - { - key_as_string: '2020-06-05T21:30:00.000Z', - key: 1591392600000, - doc_count: 42, - }, - { - key_as_string: '2020-06-05T21:40:00.000Z', - key: 1591393200000, - doc_count: 23, - }, - { - key_as_string: '2020-06-05T21:50:00.000Z', - key: 1591393800000, - doc_count: 33, - }, - { - key_as_string: '2020-06-05T22:00:00.000Z', - key: 1591394400000, - doc_count: 38, - }, - { - key_as_string: '2020-06-05T22:10:00.000Z', - key: 1591395000000, - doc_count: 30, - }, - { - key_as_string: '2020-06-05T22:20:00.000Z', - key: 1591395600000, - doc_count: 25, - }, - { - key_as_string: '2020-06-05T22:30:00.000Z', - key: 1591396200000, - doc_count: 33, - }, - { - key_as_string: '2020-06-05T22:40:00.000Z', - key: 1591396800000, - doc_count: 37, - }, - { - key_as_string: '2020-06-05T22:50:00.000Z', - key: 1591397400000, - doc_count: 43, - }, - { - key_as_string: '2020-06-05T23:00:00.000Z', - key: 1591398000000, - doc_count: 30, - }, - { - key_as_string: '2020-06-05T23:10:00.000Z', - key: 1591398600000, - doc_count: 36, - }, - { - key_as_string: '2020-06-05T23:20:00.000Z', - key: 1591399200000, - doc_count: 28, - }, - { - key_as_string: '2020-06-05T23:30:00.000Z', - key: 1591399800000, - doc_count: 39, - }, - { - key_as_string: '2020-06-05T23:40:00.000Z', - key: 1591400400000, - doc_count: 27, - }, - { - key_as_string: '2020-06-05T23:50:00.000Z', - key: 1591401000000, - doc_count: 41, - }, - { - key_as_string: '2020-06-06T00:00:00.000Z', - key: 1591401600000, - doc_count: 25, - }, - { - key_as_string: '2020-06-06T00:10:00.000Z', - key: 1591402200000, - doc_count: 31, - }, - { - key_as_string: '2020-06-06T00:20:00.000Z', - key: 1591402800000, - doc_count: 28, - }, - { - key_as_string: '2020-06-06T00:30:00.000Z', - key: 1591403400000, - doc_count: 29, - }, - { - key_as_string: '2020-06-06T00:40:00.000Z', - key: 1591404000000, - doc_count: 49, - }, - { - key_as_string: '2020-06-06T00:50:00.000Z', - key: 1591404600000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T01:00:00.000Z', - key: 1591405200000, - doc_count: 41, - }, - { - key_as_string: '2020-06-06T01:10:00.000Z', - key: 1591405800000, - doc_count: 30, - }, - { - key_as_string: '2020-06-06T01:20:00.000Z', - key: 1591406400000, - doc_count: 36, - }, - { - key_as_string: '2020-06-06T01:30:00.000Z', - key: 1591407000000, - doc_count: 39, - }, - { - key_as_string: '2020-06-06T01:40:00.000Z', - key: 1591407600000, - doc_count: 23, - }, - { - key_as_string: '2020-06-06T01:50:00.000Z', - key: 1591408200000, - doc_count: 40, - }, - { - key_as_string: '2020-06-06T02:00:00.000Z', - key: 1591408800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T02:10:00.000Z', - key: 1591409400000, - doc_count: 28, - }, - { - key_as_string: '2020-06-06T02:20:00.000Z', - key: 1591410000000, - doc_count: 33, - }, - { - key_as_string: '2020-06-06T02:30:00.000Z', - key: 1591410600000, - doc_count: 31, - }, - { - key_as_string: '2020-06-06T02:40:00.000Z', - key: 1591411200000, - doc_count: 39, - }, - { - key_as_string: '2020-06-06T02:50:00.000Z', - key: 1591411800000, - doc_count: 33, - }, - { - key_as_string: '2020-06-06T03:00:00.000Z', - key: 1591412400000, - doc_count: 35, - }, - { - key_as_string: '2020-06-06T03:10:00.000Z', - key: 1591413000000, - doc_count: 31, - }, - { - key_as_string: '2020-06-06T03:20:00.000Z', - key: 1591413600000, - doc_count: 35, - }, - { - key_as_string: '2020-06-06T03:30:00.000Z', - key: 1591414200000, - doc_count: 37, - }, - { - key_as_string: '2020-06-06T03:40:00.000Z', - key: 1591414800000, - doc_count: 26, - }, - { - key_as_string: '2020-06-06T03:50:00.000Z', - key: 1591415400000, - doc_count: 27, - }, - { - key_as_string: '2020-06-06T04:00:00.000Z', - key: 1591416000000, - doc_count: 26, - }, - { - key_as_string: '2020-06-06T04:10:00.000Z', - key: 1591416600000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T04:20:00.000Z', - key: 1591417200000, - doc_count: 33, - }, - { - key_as_string: '2020-06-06T04:30:00.000Z', - key: 1591417800000, - doc_count: 38, - }, - { - key_as_string: '2020-06-06T04:40:00.000Z', - key: 1591418400000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T04:50:00.000Z', - key: 1591419000000, - doc_count: 37, - }, - { - key_as_string: '2020-06-06T05:00:00.000Z', - key: 1591419600000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T05:10:00.000Z', - key: 1591420200000, - doc_count: 25, - }, - { - key_as_string: '2020-06-06T05:20:00.000Z', - key: 1591420800000, - doc_count: 20, - }, - { - key_as_string: '2020-06-06T05:30:00.000Z', - key: 1591421400000, - doc_count: 35, - }, - { - key_as_string: '2020-06-06T05:40:00.000Z', - key: 1591422000000, - doc_count: 41, - }, - { - key_as_string: '2020-06-06T05:50:00.000Z', - key: 1591422600000, - doc_count: 40, - }, - { - key_as_string: '2020-06-06T06:00:00.000Z', - key: 1591423200000, - doc_count: 33, - }, - { - key_as_string: '2020-06-06T06:10:00.000Z', - key: 1591423800000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T06:20:00.000Z', - key: 1591424400000, - doc_count: 44, - }, - { - key_as_string: '2020-06-06T06:30:00.000Z', - key: 1591425000000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T06:40:00.000Z', - key: 1591425600000, - doc_count: 32, - }, - { - key_as_string: '2020-06-06T06:50:00.000Z', - key: 1591426200000, - doc_count: 37, - }, - { - key_as_string: '2020-06-06T07:00:00.000Z', - key: 1591426800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T07:10:00.000Z', - key: 1591427400000, - doc_count: 28, - }, - { - key_as_string: '2020-06-06T07:20:00.000Z', - key: 1591428000000, - doc_count: 26, - }, - { - key_as_string: '2020-06-06T07:30:00.000Z', - key: 1591428600000, - doc_count: 37, - }, - { - key_as_string: '2020-06-06T07:40:00.000Z', - key: 1591429200000, - doc_count: 36, - }, - { - key_as_string: '2020-06-06T07:50:00.000Z', - key: 1591429800000, - doc_count: 37, - }, - { - key_as_string: '2020-06-06T08:00:00.000Z', - key: 1591430400000, - doc_count: 23, - }, - { - key_as_string: '2020-06-06T08:10:00.000Z', - key: 1591431000000, - doc_count: 47, - }, - { - key_as_string: '2020-06-06T08:20:00.000Z', - key: 1591431600000, - doc_count: 41, - }, - { - key_as_string: '2020-06-06T08:30:00.000Z', - key: 1591432200000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T08:40:00.000Z', - key: 1591432800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T08:50:00.000Z', - key: 1591433400000, - doc_count: 27, - }, - { - key_as_string: '2020-06-06T09:00:00.000Z', - key: 1591434000000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T09:10:00.000Z', - key: 1591434600000, - doc_count: 44, - }, - { - key_as_string: '2020-06-06T09:20:00.000Z', - key: 1591435200000, - doc_count: 20, - }, - { - key_as_string: '2020-06-06T09:30:00.000Z', - key: 1591435800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T09:40:00.000Z', - key: 1591436400000, - doc_count: 29, - }, - { - key_as_string: '2020-06-06T09:50:00.000Z', - key: 1591437000000, - doc_count: 28, - }, - { - key_as_string: '2020-06-06T10:00:00.000Z', - key: 1591437600000, - doc_count: 36, - }, - { - key_as_string: '2020-06-06T10:10:00.000Z', - key: 1591438200000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T10:20:00.000Z', - key: 1591438800000, - doc_count: 26, - }, - { - key_as_string: '2020-06-06T10:30:00.000Z', - key: 1591439400000, - doc_count: 29, - }, - { - key_as_string: '2020-06-06T10:40:00.000Z', - key: 1591440000000, - doc_count: 45, - }, - { - key_as_string: '2020-06-06T10:50:00.000Z', - key: 1591440600000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T11:00:00.000Z', - key: 1591441200000, - doc_count: 25, - }, - { - key_as_string: '2020-06-06T11:10:00.000Z', - key: 1591441800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T11:20:00.000Z', - key: 1591442400000, - doc_count: 28, - }, - { - key_as_string: '2020-06-06T11:30:00.000Z', - key: 1591443000000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T11:40:00.000Z', - key: 1591443600000, - doc_count: 31, - }, - { - key_as_string: '2020-06-06T11:50:00.000Z', - key: 1591444200000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T12:00:00.000Z', - key: 1591444800000, - doc_count: 34, - }, - { - key_as_string: '2020-06-06T12:10:00.000Z', - key: 1591445400000, - doc_count: 21, - }, - { - key_as_string: '2020-06-06T12:20:00.000Z', - key: 1591446000000, - doc_count: 40, - }, - { - key_as_string: '2020-06-06T12:30:00.000Z', - key: 1591446600000, - doc_count: 37, - }, - { - key_as_string: '2020-06-06T12:40:00.000Z', - key: 1591447200000, - doc_count: 31, - }, - { - key_as_string: '2020-06-06T12:50:00.000Z', - key: 1591447800000, - doc_count: 21, - }, - { - key_as_string: '2020-06-06T13:00:00.000Z', - key: 1591448400000, - doc_count: 24, - }, - { - key_as_string: '2020-06-06T13:10:00.000Z', - key: 1591449000000, - doc_count: 30, - }, - { - key_as_string: '2020-06-06T13:20:00.000Z', - key: 1591449600000, - doc_count: 22, - }, - { - key_as_string: '2020-06-06T13:30:00.000Z', - key: 1591450200000, - doc_count: 27, - }, - { - key_as_string: '2020-06-06T13:40:00.000Z', - key: 1591450800000, - doc_count: 30, - }, - { - key_as_string: '2020-06-06T13:50:00.000Z', - key: 1591451400000, - doc_count: 22, - }, - { - key_as_string: '2020-06-06T14:00:00.000Z', - key: 1591452000000, - doc_count: 9, - }, -]; - -export const errors = [ - { - key_as_string: '2020-06-05T14:00:00.000Z', - key: 1591365600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T14:10:00.000Z', - key: 1591366200000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T14:20:00.000Z', - key: 1591366800000, - doc_count: 1, - }, - { - key_as_string: '2020-06-05T14:30:00.000Z', - key: 1591367400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T14:40:00.000Z', - key: 1591368000000, - doc_count: 8, - }, - { - key_as_string: '2020-06-05T14:50:00.000Z', - key: 1591368600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T15:00:00.000Z', - key: 1591369200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T15:10:00.000Z', - key: 1591369800000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T15:20:00.000Z', - key: 1591370400000, - doc_count: 9, - }, - { - key_as_string: '2020-06-05T15:30:00.000Z', - key: 1591371000000, - doc_count: 1, - }, - { - key_as_string: '2020-06-05T15:40:00.000Z', - key: 1591371600000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T15:50:00.000Z', - key: 1591372200000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T16:00:00.000Z', - key: 1591372800000, - doc_count: 8, - }, - { - key_as_string: '2020-06-05T16:10:00.000Z', - key: 1591373400000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T16:20:00.000Z', - key: 1591374000000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T16:30:00.000Z', - key: 1591374600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T16:40:00.000Z', - key: 1591375200000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T16:50:00.000Z', - key: 1591375800000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T17:00:00.000Z', - key: 1591376400000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T17:10:00.000Z', - key: 1591377000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T17:20:00.000Z', - key: 1591377600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T17:30:00.000Z', - key: 1591378200000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T17:40:00.000Z', - key: 1591378800000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T17:50:00.000Z', - key: 1591379400000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T18:00:00.000Z', - key: 1591380000000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T18:10:00.000Z', - key: 1591380600000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T18:20:00.000Z', - key: 1591381200000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T18:30:00.000Z', - key: 1591381800000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T18:40:00.000Z', - key: 1591382400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T18:50:00.000Z', - key: 1591383000000, - doc_count: 8, - }, - { - key_as_string: '2020-06-05T19:00:00.000Z', - key: 1591383600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T19:10:00.000Z', - key: 1591384200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T19:20:00.000Z', - key: 1591384800000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T19:30:00.000Z', - key: 1591385400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T19:40:00.000Z', - key: 1591386000000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T19:50:00.000Z', - key: 1591386600000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T20:00:00.000Z', - key: 1591387200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T20:10:00.000Z', - key: 1591387800000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T20:20:00.000Z', - key: 1591388400000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T20:30:00.000Z', - key: 1591389000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T20:40:00.000Z', - key: 1591389600000, - doc_count: 1, - }, - { - key_as_string: '2020-06-05T20:50:00.000Z', - key: 1591390200000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T21:00:00.000Z', - key: 1591390800000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T21:10:00.000Z', - key: 1591391400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T21:20:00.000Z', - key: 1591392000000, - doc_count: 6, - }, - { - key_as_string: '2020-06-05T21:30:00.000Z', - key: 1591392600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T21:40:00.000Z', - key: 1591393200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T21:50:00.000Z', - key: 1591393800000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T22:00:00.000Z', - key: 1591394400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T22:10:00.000Z', - key: 1591395000000, - doc_count: 2, - }, - { - key_as_string: '2020-06-05T22:20:00.000Z', - key: 1591395600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T22:30:00.000Z', - key: 1591396200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T22:40:00.000Z', - key: 1591396800000, - doc_count: 8, - }, - { - key_as_string: '2020-06-05T22:50:00.000Z', - key: 1591397400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-05T23:00:00.000Z', - key: 1591398000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T23:10:00.000Z', - key: 1591398600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T23:20:00.000Z', - key: 1591399200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-05T23:30:00.000Z', - key: 1591399800000, - doc_count: 7, - }, - { - key_as_string: '2020-06-05T23:40:00.000Z', - key: 1591400400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-05T23:50:00.000Z', - key: 1591401000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T00:00:00.000Z', - key: 1591401600000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T00:10:00.000Z', - key: 1591402200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T00:20:00.000Z', - key: 1591402800000, - doc_count: 1, - }, - { - key_as_string: '2020-06-06T00:30:00.000Z', - key: 1591403400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T00:40:00.000Z', - key: 1591404000000, - doc_count: 7, - }, - { - key_as_string: '2020-06-06T00:50:00.000Z', - key: 1591404600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T01:00:00.000Z', - key: 1591405200000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T01:10:00.000Z', - key: 1591405800000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T01:20:00.000Z', - key: 1591406400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T01:30:00.000Z', - key: 1591407000000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T01:40:00.000Z', - key: 1591407600000, - doc_count: 0, - }, - { - key_as_string: '2020-06-06T01:50:00.000Z', - key: 1591408200000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T02:00:00.000Z', - key: 1591408800000, - doc_count: 9, - }, - { - key_as_string: '2020-06-06T02:10:00.000Z', - key: 1591409400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T02:20:00.000Z', - key: 1591410000000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T02:30:00.000Z', - key: 1591410600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T02:40:00.000Z', - key: 1591411200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T02:50:00.000Z', - key: 1591411800000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T03:00:00.000Z', - key: 1591412400000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T03:10:00.000Z', - key: 1591413000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T03:20:00.000Z', - key: 1591413600000, - doc_count: 8, - }, - { - key_as_string: '2020-06-06T03:30:00.000Z', - key: 1591414200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T03:40:00.000Z', - key: 1591414800000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T03:50:00.000Z', - key: 1591415400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T04:00:00.000Z', - key: 1591416000000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T04:10:00.000Z', - key: 1591416600000, - doc_count: 7, - }, - { - key_as_string: '2020-06-06T04:20:00.000Z', - key: 1591417200000, - doc_count: 7, - }, - { - key_as_string: '2020-06-06T04:30:00.000Z', - key: 1591417800000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T04:40:00.000Z', - key: 1591418400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T04:50:00.000Z', - key: 1591419000000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T05:00:00.000Z', - key: 1591419600000, - doc_count: 7, - }, - { - key_as_string: '2020-06-06T05:10:00.000Z', - key: 1591420200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T05:20:00.000Z', - key: 1591420800000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T05:30:00.000Z', - key: 1591421400000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T05:40:00.000Z', - key: 1591422000000, - doc_count: 8, - }, - { - key_as_string: '2020-06-06T05:50:00.000Z', - key: 1591422600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T06:00:00.000Z', - key: 1591423200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T06:10:00.000Z', - key: 1591423800000, - doc_count: 1, - }, - { - key_as_string: '2020-06-06T06:20:00.000Z', - key: 1591424400000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T06:30:00.000Z', - key: 1591425000000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T06:40:00.000Z', - key: 1591425600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T06:50:00.000Z', - key: 1591426200000, - doc_count: 8, - }, - { - key_as_string: '2020-06-06T07:00:00.000Z', - key: 1591426800000, - doc_count: 8, - }, - { - key_as_string: '2020-06-06T07:10:00.000Z', - key: 1591427400000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T07:20:00.000Z', - key: 1591428000000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T07:30:00.000Z', - key: 1591428600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T07:40:00.000Z', - key: 1591429200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T07:50:00.000Z', - key: 1591429800000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T08:00:00.000Z', - key: 1591430400000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T08:10:00.000Z', - key: 1591431000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T08:20:00.000Z', - key: 1591431600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T08:30:00.000Z', - key: 1591432200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T08:40:00.000Z', - key: 1591432800000, - doc_count: 1, - }, - { - key_as_string: '2020-06-06T08:50:00.000Z', - key: 1591433400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T09:00:00.000Z', - key: 1591434000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T09:10:00.000Z', - key: 1591434600000, - doc_count: 9, - }, - { - key_as_string: '2020-06-06T09:20:00.000Z', - key: 1591435200000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T09:30:00.000Z', - key: 1591435800000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T09:40:00.000Z', - key: 1591436400000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T09:50:00.000Z', - key: 1591437000000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T10:00:00.000Z', - key: 1591437600000, - doc_count: 1, - }, - { - key_as_string: '2020-06-06T10:10:00.000Z', - key: 1591438200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T10:20:00.000Z', - key: 1591438800000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T10:30:00.000Z', - key: 1591439400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T10:40:00.000Z', - key: 1591440000000, - doc_count: 7, - }, - { - key_as_string: '2020-06-06T10:50:00.000Z', - key: 1591440600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T11:00:00.000Z', - key: 1591441200000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T11:10:00.000Z', - key: 1591441800000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T11:20:00.000Z', - key: 1591442400000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T11:30:00.000Z', - key: 1591443000000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T11:40:00.000Z', - key: 1591443600000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T11:50:00.000Z', - key: 1591444200000, - doc_count: 3, - }, - { - key_as_string: '2020-06-06T12:00:00.000Z', - key: 1591444800000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T12:10:00.000Z', - key: 1591445400000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T12:20:00.000Z', - key: 1591446000000, - doc_count: 6, - }, - { - key_as_string: '2020-06-06T12:30:00.000Z', - key: 1591446600000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T12:40:00.000Z', - key: 1591447200000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T12:50:00.000Z', - key: 1591447800000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T13:00:00.000Z', - key: 1591448400000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T13:10:00.000Z', - key: 1591449000000, - doc_count: 4, - }, - { - key_as_string: '2020-06-06T13:20:00.000Z', - key: 1591449600000, - doc_count: 5, - }, - { - key_as_string: '2020-06-06T13:30:00.000Z', - key: 1591450200000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T13:40:00.000Z', - key: 1591450800000, - doc_count: 2, - }, - { - key_as_string: '2020-06-06T13:50:00.000Z', - key: 1591451400000, - doc_count: 1, - }, - { - key_as_string: '2020-06-06T14:00:00.000Z', - key: 1591452000000, - doc_count: 2, - }, -]; - export const apmData = [ { time: 1591365600000, diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index e7d2f78b1fe09..27642f1dcc68f 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -94,9 +94,6 @@ export const Overview = () => { showLegend legendPosition="bottom" xDomain={{ min: startAPM, max: endAPM }} - onElementOver={(x) => { - console.log('##########3', x[0][0].x); - }} /> <Axis id="bottom" diff --git a/x-pack/plugins/observability/public/typings/chart/index.ts b/x-pack/plugins/observability/public/typings/chart/index.ts new file mode 100644 index 0000000000000..bee949b47646a --- /dev/null +++ b/x-pack/plugins/observability/public/typings/chart/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ChartData { + time: number; + value: number; + group: string; +} + +export type FetchChartData = ({ start, end }: { start: number; end: number }) => ChartData[]; From ded79940a92b2e2ce061b2d9e173ecccbf6921a5 Mon Sep 17 00:00:00 2001 From: cauemarcondes <caue.marcondes@elastic.co> Date: Wed, 10 Jun 2020 08:57:32 +0200 Subject: [PATCH 28/97] refactoring --- .../public/components/header/index.tsx | 9 +- .../public/components/layout/with_header.tsx | 18 +- .../observability/public/pages/home/index.tsx | 257 ++++++++---------- .../public/pages/overview/index.tsx | 186 ++++++++++++- .../public/typings/chart/index.ts | 13 - 5 files changed, 312 insertions(+), 171 deletions(-) delete mode 100644 x-pack/plugins/observability/public/typings/chart/index.ts diff --git a/x-pack/plugins/observability/public/components/header/index.tsx b/x-pack/plugins/observability/public/components/header/index.tsx index 828afdf1c9099..0e7417cd146c8 100644 --- a/x-pack/plugins/observability/public/components/header/index.tsx +++ b/x-pack/plugins/observability/public/components/header/index.tsx @@ -14,21 +14,22 @@ const Container = styled.div<{ color: string }>` border-bottom: ${(props) => props.theme.eui.euiBorderThin}; `; -const Wrapper = styled.div<{ maxWidth?: number }>` +const Wrapper = styled.div<{ restrictWidth?: number }>` width: 100%; - max-width: 1200px; + max-width: ${(props) => `${props.restrictWidth}px`}; margin: 0 auto; overflow: hidden; `; interface Props { color: string; + restrictWidth?: number; } -export const Header = ({ color }: Props) => { +export const Header = ({ color, restrictWidth }: Props) => { return ( <Container color={color}> - <Wrapper> + <Wrapper restrictWidth={restrictWidth}> <EuiSpacer size="xxl" /> <EuiFlexGroup> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/observability/public/components/layout/with_header.tsx b/x-pack/plugins/observability/public/components/layout/with_header.tsx index 07c7b39002f9c..ab86803b8434c 100644 --- a/x-pack/plugins/observability/public/components/layout/with_header.tsx +++ b/x-pack/plugins/observability/public/components/layout/with_header.tsx @@ -9,7 +9,12 @@ import styled from 'styled-components'; import { EuiPage, EuiPageBody, EuiSpacer, EuiPageProps } from '@elastic/eui'; import { Header } from '../header/index'; -const Page = styled(EuiPage)<EuiPageProps & { color: string }>` +const Page = styled(EuiPage)<EuiPageProps>` + background: transparent; +`; + +const Container = styled.div<{ color?: string }>` + min-height: calc(100vh - ${(props) => props.theme.eui.euiHeaderChildSize}); background: ${(props) => props.color}; `; @@ -17,16 +22,17 @@ interface Props { headerColor: string; bodyColor: string; children?: React.ReactNode; + restrictWidth?: number; } -export const WithHeaderLayout = ({ headerColor, bodyColor, children }: Props) => ( - <> - <Header color={headerColor} /> - <Page color={bodyColor} restrictWidth={1200}> +export const WithHeaderLayout = ({ headerColor, bodyColor, children, restrictWidth }: Props) => ( + <Container color={bodyColor}> + <Header color={headerColor} restrictWidth={restrictWidth} /> + <Page restrictWidth={restrictWidth}> <EuiPageBody> <EuiSpacer size="m" /> {children} </EuiPageBody> </Page> - </> + </Container> ); diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 4967bcb7a7618..9c64ca2a17016 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -18,28 +18,11 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import styled from 'styled-components'; +import React, { useEffect, useContext } from 'react'; +import styled, { ThemeContext } from 'styled-components'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { appsSection, tryItOutItemsSection } from './section'; - -const Container = styled.div` - min-height: calc(100vh - 48px); - background: ${(props) => props.theme.eui.euiColorEmptyShade}; -`; - -const Title = styled.div` - background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; - border-bottom: ${(props) => props.theme.eui.euiBorderThin}; -`; - -const Page = styled.div` - width: 100%; - max-width: 1200px; - margin: 0 auto; - overflow: hidden; -} -`; +import { WithHeaderLayout } from '../../components/layout/with_header'; const EuiCardWithoutPadding = styled(EuiCard)` padding: 0; @@ -47,6 +30,7 @@ const EuiCardWithoutPadding = styled(EuiCard)` export const Home = () => { const { core } = usePluginContext(); + const theme = useContext(ThemeContext); useEffect(() => { core.chrome.setBreadcrumbs([ @@ -66,142 +50,123 @@ export const Home = () => { const apps = appsSection.filter((app) => app.id !== 'alert'); return ( - <Container> - <Title> - <Page> - <EuiSpacer size="xxl" /> + <WithHeaderLayout + restrictWidth={1200} + headerColor={theme.eui.euiPageBackgroundColor} + bodyColor={theme.eui.euiColorEmptyShade} + > + <EuiFlexGroup direction="column"> + {/* title and description */} + <EuiFlexItem style={{ maxWidth: '50%' }}> + <EuiTitle size="s"> + <h2> + {i18n.translate('xpack.observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', + })} + </h2> + </EuiTitle> + <EuiSpacer size="m" /> + <EuiText size="s" color="subdued"> + {i18n.translate('xpack.observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + </EuiText> + </EuiFlexItem> + + {/* Apps sections */} + <EuiFlexItem> + <EuiSpacer size="s" /> <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiIcon type="logoObservability" size="xxl" /> + <EuiFlexItem> + <EuiFlexGrid columns={2}> + {apps.map((app) => ( + <EuiFlexItem> + <EuiCardWithoutPadding + display="plain" + layout="horizontal" + icon={<EuiIcon size="l" type={app.icon} />} + title={ + <EuiTitle size="xs" className="title"> + <h3>{app.title}</h3> + </EuiTitle> + } + description={app.description} + /> + </EuiFlexItem> + ))} + </EuiFlexGrid> </EuiFlexItem> <EuiFlexItem> - <EuiTitle size="m"> - <h1> - {i18n.translate('xpack.observability.home.title', { - defaultMessage: 'Observability', - })} - </h1> - </EuiTitle> + <EuiImage + size="xl" + alt="observability overview image" + url={core.http.basePath.prepend( + '/plugins/observability/assets/observability_overview.png' + )} + /> </EuiFlexItem> </EuiFlexGroup> - <EuiSpacer size="xxl" /> - </Page> - - - - - {/* title and description */} - - -

- {i18n.translate('xpack.observability.home.sectionTitle', { - defaultMessage: 'Observability built on the Elastic Stack', + + + {/* Get started button */} + + + + + {i18n.translate('xpack.observability.home.getStatedButton', { + defaultMessage: 'Get started', })} -

-
- - - {i18n.translate('xpack.observability.home.sectionsubtitle', { - defaultMessage: - 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', - })} - -
+ + +
+ - {/* Apps sections */} - - - - - - {apps.map((app) => ( - - } - title={ - -

{app.title}

-
- } - description={app.description} - /> -
- ))} -
-
- - - -
-
+ - {/* Get started button */} - - - - - {i18n.translate('xpack.observability.home.getStatedButton', { - defaultMessage: 'Get started', + {/* Try it out */} + + + + +

+ {i18n.translate('xpack.observability.home.tryItOut', { + defaultMessage: 'Try it out', })} - - - - - - +

+
+
+
+
- {/* Try it out */} - - - - -

- {i18n.translate('xpack.observability.home.tryItOut', { - defaultMessage: 'Try it out', - })} -

-
+ {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + } + title={ + +

{item.title}

+
+ } + description={item.description} + target={item.target} + href={item.href} + />
-
-
- - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - } - title={ - -

{item.title}

-
- } - description={item.description} - target={item.target} - href={item.href} - /> -
- ))} -
- -
-
-
-
+ ))} + + + + + ); }; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 27642f1dcc68f..1ba90c2fb4a7b 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -80,7 +80,189 @@ export const Overview = () => { onChange={(e) => setWithAlert((currState) => !currState)} /> - + + + + + + + { + console.log('#### Logs', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + /> + + numeral(d).format('0a')} + /> + + + + + + + + + { + console.log('#### Metrics', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + /> + + numeral(d).format('0a')} + /> + + + + + + + + { + console.log('#### APM', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend={true} + legendPosition="bottom" + /> + + + + `${Number(d).toFixed(0)} %`} + groupId="errors" + /> + numeral(d).format('0a')} + /> + + + + + + + { + console.log('#### Uptime', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + /> + + numeral(d).format('0a')} + /> + + + + + + + + {withAlert && ( + + chart goes here + + )} + + {/* @@ -264,7 +446,7 @@ export const Overview = () => { )} - + */} diff --git a/x-pack/plugins/observability/public/typings/chart/index.ts b/x-pack/plugins/observability/public/typings/chart/index.ts deleted file mode 100644 index bee949b47646a..0000000000000 --- a/x-pack/plugins/observability/public/typings/chart/index.ts +++ /dev/null @@ -1,13 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export interface ChartData { - time: number; - value: number; - group: string; -} - -export type FetchChartData = ({ start, end }: { start: number; end: number }) => ChartData[]; From 9870857fe12113fec3a6acd23f6cc30a24c10e42 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 12 Jun 2020 08:57:54 +0200 Subject: [PATCH 29/97] crearting apm chart --- .../chart/apm/apm.data.js} | 21 +- .../public/components/chart/apm/index.tsx | 82 +++++ .../public/components/chart/apm/mock.data.ts | 344 ++++++++++++++++++ .../public/components/header/index.tsx | 1 + .../public/components/layout/with_header.tsx | 4 + .../public/pages/overview/index.tsx | 298 ++------------- 6 files changed, 473 insertions(+), 277 deletions(-) rename x-pack/plugins/observability/public/{pages/overview/apm.data.ts => components/chart/apm/apm.data.js} (97%) create mode 100644 x-pack/plugins/observability/public/components/chart/apm/index.tsx create mode 100644 x-pack/plugins/observability/public/components/chart/apm/mock.data.ts diff --git a/x-pack/plugins/observability/public/pages/overview/apm.data.ts b/x-pack/plugins/observability/public/components/chart/apm/apm.data.js similarity index 97% rename from x-pack/plugins/observability/public/pages/overview/apm.data.ts rename to x-pack/plugins/observability/public/components/chart/apm/apm.data.js index 576e9dd1867e2..6c9d585e61b88 100644 --- a/x-pack/plugins/observability/public/pages/overview/apm.data.ts +++ b/x-pack/plugins/observability/public/components/chart/apm/apm.data.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const apmData = [ +const apmData = [ { time: 1591365600000, value: 32, @@ -1456,3 +1456,22 @@ export const apmData = [ group: 'error', }, ]; + +const _ = require('lodash'); + +const grouped = _.groupBy(apmData, 'group'); +console.log('### caue: grouped', grouped); + +const series = Object.keys(grouped).map((key) => { + const data = grouped[key]; + const coordinates = data.map((d) => ({ x: d.time, y: d.value })); + return { + key, + label: key, + coordinates, + }; +}); + +console.log('### caue: series', JSON.stringify(series)); + +// console.log('### caue: coordinates', coordinates); diff --git a/x-pack/plugins/observability/public/components/chart/apm/index.tsx b/x-pack/plugins/observability/public/components/chart/apm/index.tsx new file mode 100644 index 0000000000000..f7aa9f94fa21f --- /dev/null +++ b/x-pack/plugins/observability/public/components/chart/apm/index.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { + Chart, + Settings, + BarSeries, + LineSeries, + Axis, + Position, + DARK_THEME, + LIGHT_THEME, + niceTimeFormatter, +} from '@elastic/charts'; +import { ThemeContext } from 'styled-components'; +import numeral from '@elastic/numeral'; +import { ChartContainer } from '../container'; +import { data } from './mock.data'; + +export const APMChart = () => { + const theme = useContext(ThemeContext); + + const transactionSeries = data.series.find((d) => d.key === 'transaction')!; + const errorSeries = data.series.find((d) => d.key === 'error')!; + + const startAPM = transactionSeries.coordinates[0].x; + const endAPM = transactionSeries.coordinates[transactionSeries.coordinates.length - 1].x; + const formatterAPM = niceTimeFormatter([startAPM, endAPM]); + + return ( + + + { + console.log('#### APM', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend={true} + legendPosition="bottom" + /> + + + + `${Number(d).toFixed(0)} %`} + groupId="errors" + /> + numeral(d).format('0a')} + /> + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts b/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts new file mode 100644 index 0000000000000..486e07dc787d9 --- /dev/null +++ b/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts @@ -0,0 +1,344 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface Stat { + label: string; + value: number; + color?: string; +} + +interface Coordinates { + x: number; + y: number; +} + +interface Series { + label: string; + coordinates: Coordinates[]; + color?: string; + key?: string; +} + +interface FetcherResponse { + title: string; + appLink: string; + stats: Stat[]; + series: Series[]; +} + +export const data: FetcherResponse = { + title: 'Apm', + appLink: '', + stats: [ + { label: 'Services', value: 14 }, + { label: 'Transactions', value: 312000 }, + { label: 'Error rate', value: 0.14 }, + ], + series: [ + { + key: 'transaction', + label: 'transaction', + coordinates: [ + { x: 1591365600000, y: 32 }, + { x: 1591366200000, y: 43 }, + { x: 1591366800000, y: 22 }, + { x: 1591367400000, y: 29 }, + { x: 1591368000000, y: 39 }, + { x: 1591368600000, y: 36 }, + { x: 1591369200000, y: 50 }, + { x: 1591369800000, y: 31 }, + { x: 1591370400000, y: 39 }, + { x: 1591371000000, y: 26 }, + { x: 1591371600000, y: 45 }, + { x: 1591372200000, y: 27 }, + { x: 1591372800000, y: 37 }, + { x: 1591373400000, y: 55 }, + { x: 1591374000000, y: 31 }, + { x: 1591374600000, y: 26 }, + { x: 1591375200000, y: 57 }, + { x: 1591375800000, y: 25 }, + { x: 1591376400000, y: 28 }, + { x: 1591377000000, y: 40 }, + { x: 1591377600000, y: 33 }, + { x: 1591378200000, y: 33 }, + { x: 1591378800000, y: 31 }, + { x: 1591379400000, y: 32 }, + { x: 1591380000000, y: 34 }, + { x: 1591380600000, y: 31 }, + { x: 1591381200000, y: 16 }, + { x: 1591381800000, y: 34 }, + { x: 1591382400000, y: 33 }, + { x: 1591383000000, y: 35 }, + { x: 1591383600000, y: 47 }, + { x: 1591384200000, y: 44 }, + { x: 1591384800000, y: 21 }, + { x: 1591385400000, y: 25 }, + { x: 1591386000000, y: 34 }, + { x: 1591386600000, y: 37 }, + { x: 1591387200000, y: 38 }, + { x: 1591387800000, y: 28 }, + { x: 1591388400000, y: 32 }, + { x: 1591389000000, y: 37 }, + { x: 1591389600000, y: 25 }, + { x: 1591390200000, y: 33 }, + { x: 1591390800000, y: 34 }, + { x: 1591391400000, y: 30 }, + { x: 1591392000000, y: 45 }, + { x: 1591392600000, y: 42 }, + { x: 1591393200000, y: 23 }, + { x: 1591393800000, y: 33 }, + { x: 1591394400000, y: 38 }, + { x: 1591395000000, y: 30 }, + { x: 1591395600000, y: 25 }, + { x: 1591396200000, y: 33 }, + { x: 1591396800000, y: 37 }, + { x: 1591397400000, y: 43 }, + { x: 1591398000000, y: 30 }, + { x: 1591398600000, y: 36 }, + { x: 1591399200000, y: 28 }, + { x: 1591399800000, y: 39 }, + { x: 1591400400000, y: 27 }, + { x: 1591401000000, y: 41 }, + { x: 1591401600000, y: 25 }, + { x: 1591402200000, y: 31 }, + { x: 1591402800000, y: 28 }, + { x: 1591403400000, y: 29 }, + { x: 1591404000000, y: 49 }, + { x: 1591404600000, y: 24 }, + { x: 1591405200000, y: 41 }, + { x: 1591405800000, y: 30 }, + { x: 1591406400000, y: 36 }, + { x: 1591407000000, y: 39 }, + { x: 1591407600000, y: 23 }, + { x: 1591408200000, y: 40 }, + { x: 1591408800000, y: 34 }, + { x: 1591409400000, y: 28 }, + { x: 1591410000000, y: 33 }, + { x: 1591410600000, y: 31 }, + { x: 1591411200000, y: 39 }, + { x: 1591411800000, y: 33 }, + { x: 1591412400000, y: 35 }, + { x: 1591413000000, y: 31 }, + { x: 1591413600000, y: 35 }, + { x: 1591414200000, y: 37 }, + { x: 1591414800000, y: 26 }, + { x: 1591415400000, y: 27 }, + { x: 1591416000000, y: 26 }, + { x: 1591416600000, y: 34 }, + { x: 1591417200000, y: 33 }, + { x: 1591417800000, y: 38 }, + { x: 1591418400000, y: 34 }, + { x: 1591419000000, y: 37 }, + { x: 1591419600000, y: 24 }, + { x: 1591420200000, y: 25 }, + { x: 1591420800000, y: 20 }, + { x: 1591421400000, y: 35 }, + { x: 1591422000000, y: 41 }, + { x: 1591422600000, y: 40 }, + { x: 1591423200000, y: 33 }, + { x: 1591423800000, y: 24 }, + { x: 1591424400000, y: 44 }, + { x: 1591425000000, y: 24 }, + { x: 1591425600000, y: 32 }, + { x: 1591426200000, y: 37 }, + { x: 1591426800000, y: 34 }, + { x: 1591427400000, y: 28 }, + { x: 1591428000000, y: 26 }, + { x: 1591428600000, y: 37 }, + { x: 1591429200000, y: 36 }, + { x: 1591429800000, y: 37 }, + { x: 1591430400000, y: 23 }, + { x: 1591431000000, y: 47 }, + { x: 1591431600000, y: 41 }, + { x: 1591432200000, y: 24 }, + { x: 1591432800000, y: 34 }, + { x: 1591433400000, y: 27 }, + { x: 1591434000000, y: 34 }, + { x: 1591434600000, y: 44 }, + { x: 1591435200000, y: 20 }, + { x: 1591435800000, y: 34 }, + { x: 1591436400000, y: 29 }, + { x: 1591437000000, y: 28 }, + { x: 1591437600000, y: 36 }, + { x: 1591438200000, y: 34 }, + { x: 1591438800000, y: 26 }, + { x: 1591439400000, y: 29 }, + { x: 1591440000000, y: 45 }, + { x: 1591440600000, y: 34 }, + { x: 1591441200000, y: 25 }, + { x: 1591441800000, y: 34 }, + { x: 1591442400000, y: 28 }, + { x: 1591443000000, y: 34 }, + { x: 1591443600000, y: 31 }, + { x: 1591444200000, y: 24 }, + { x: 1591444800000, y: 34 }, + { x: 1591445400000, y: 21 }, + { x: 1591446000000, y: 40 }, + { x: 1591446600000, y: 37 }, + { x: 1591447200000, y: 31 }, + { x: 1591447800000, y: 21 }, + { x: 1591448400000, y: 24 }, + { x: 1591449000000, y: 30 }, + { x: 1591449600000, y: 22 }, + { x: 1591450200000, y: 27 }, + { x: 1591450800000, y: 30 }, + { x: 1591451400000, y: 22 }, + { x: 1591452000000, y: 9 }, + ], + }, + { + key: 'error', + label: 'error', + coordinates: [ + { x: 1591365600000, y: 5 }, + { x: 1591366200000, y: 4 }, + { x: 1591366800000, y: 1 }, + { x: 1591367400000, y: 5 }, + { x: 1591368000000, y: 8 }, + { x: 1591368600000, y: 4 }, + { x: 1591369200000, y: 3 }, + { x: 1591369800000, y: 6 }, + { x: 1591370400000, y: 9 }, + { x: 1591371000000, y: 1 }, + { x: 1591371600000, y: 7 }, + { x: 1591372200000, y: 4 }, + { x: 1591372800000, y: 8 }, + { x: 1591373400000, y: 6 }, + { x: 1591374000000, y: 3 }, + { x: 1591374600000, y: 5 }, + { x: 1591375200000, y: 7 }, + { x: 1591375800000, y: 5 }, + { x: 1591376400000, y: 6 }, + { x: 1591377000000, y: 4 }, + { x: 1591377600000, y: 4 }, + { x: 1591378200000, y: 7 }, + { x: 1591378800000, y: 5 }, + { x: 1591379400000, y: 7 }, + { x: 1591380000000, y: 6 }, + { x: 1591380600000, y: 3 }, + { x: 1591381200000, y: 4 }, + { x: 1591381800000, y: 5 }, + { x: 1591382400000, y: 4 }, + { x: 1591383000000, y: 8 }, + { x: 1591383600000, y: 5 }, + { x: 1591384200000, y: 5 }, + { x: 1591384800000, y: 4 }, + { x: 1591385400000, y: 5 }, + { x: 1591386000000, y: 5 }, + { x: 1591386600000, y: 7 }, + { x: 1591387200000, y: 5 }, + { x: 1591387800000, y: 6 }, + { x: 1591388400000, y: 6 }, + { x: 1591389000000, y: 4 }, + { x: 1591389600000, y: 1 }, + { x: 1591390200000, y: 6 }, + { x: 1591390800000, y: 7 }, + { x: 1591391400000, y: 5 }, + { x: 1591392000000, y: 6 }, + { x: 1591392600000, y: 5 }, + { x: 1591393200000, y: 3 }, + { x: 1591393800000, y: 3 }, + { x: 1591394400000, y: 4 }, + { x: 1591395000000, y: 2 }, + { x: 1591395600000, y: 4 }, + { x: 1591396200000, y: 3 }, + { x: 1591396800000, y: 8 }, + { x: 1591397400000, y: 5 }, + { x: 1591398000000, y: 4 }, + { x: 1591398600000, y: 4 }, + { x: 1591399200000, y: 3 }, + { x: 1591399800000, y: 7 }, + { x: 1591400400000, y: 4 }, + { x: 1591401000000, y: 4 }, + { x: 1591401600000, y: 3 }, + { x: 1591402200000, y: 3 }, + { x: 1591402800000, y: 1 }, + { x: 1591403400000, y: 5 }, + { x: 1591404000000, y: 7 }, + { x: 1591404600000, y: 5 }, + { x: 1591405200000, y: 6 }, + { x: 1591405800000, y: 3 }, + { x: 1591406400000, y: 5 }, + { x: 1591407000000, y: 5 }, + { x: 1591407600000, y: 0 }, + { x: 1591408200000, y: 4 }, + { x: 1591408800000, y: 9 }, + { x: 1591409400000, y: 5 }, + { x: 1591410000000, y: 6 }, + { x: 1591410600000, y: 4 }, + { x: 1591411200000, y: 5 }, + { x: 1591411800000, y: 5 }, + { x: 1591412400000, y: 2 }, + { x: 1591413000000, y: 4 }, + { x: 1591413600000, y: 8 }, + { x: 1591414200000, y: 5 }, + { x: 1591414800000, y: 4 }, + { x: 1591415400000, y: 4 }, + { x: 1591416000000, y: 3 }, + { x: 1591416600000, y: 7 }, + { x: 1591417200000, y: 7 }, + { x: 1591417800000, y: 6 }, + { x: 1591418400000, y: 5 }, + { x: 1591419000000, y: 5 }, + { x: 1591419600000, y: 7 }, + { x: 1591420200000, y: 5 }, + { x: 1591420800000, y: 2 }, + { x: 1591421400000, y: 3 }, + { x: 1591422000000, y: 8 }, + { x: 1591422600000, y: 5 }, + { x: 1591423200000, y: 5 }, + { x: 1591423800000, y: 1 }, + { x: 1591424400000, y: 3 }, + { x: 1591425000000, y: 5 }, + { x: 1591425600000, y: 4 }, + { x: 1591426200000, y: 8 }, + { x: 1591426800000, y: 8 }, + { x: 1591427400000, y: 5 }, + { x: 1591428000000, y: 3 }, + { x: 1591428600000, y: 4 }, + { x: 1591429200000, y: 3 }, + { x: 1591429800000, y: 5 }, + { x: 1591430400000, y: 3 }, + { x: 1591431000000, y: 4 }, + { x: 1591431600000, y: 4 }, + { x: 1591432200000, y: 5 }, + { x: 1591432800000, y: 1 }, + { x: 1591433400000, y: 4 }, + { x: 1591434000000, y: 4 }, + { x: 1591434600000, y: 9 }, + { x: 1591435200000, y: 4 }, + { x: 1591435800000, y: 4 }, + { x: 1591436400000, y: 3 }, + { x: 1591437000000, y: 6 }, + { x: 1591437600000, y: 1 }, + { x: 1591438200000, y: 5 }, + { x: 1591438800000, y: 4 }, + { x: 1591439400000, y: 4 }, + { x: 1591440000000, y: 7 }, + { x: 1591440600000, y: 5 }, + { x: 1591441200000, y: 6 }, + { x: 1591441800000, y: 6 }, + { x: 1591442400000, y: 4 }, + { x: 1591443000000, y: 5 }, + { x: 1591443600000, y: 3 }, + { x: 1591444200000, y: 3 }, + { x: 1591444800000, y: 4 }, + { x: 1591445400000, y: 2 }, + { x: 1591446000000, y: 6 }, + { x: 1591446600000, y: 4 }, + { x: 1591447200000, y: 5 }, + { x: 1591447800000, y: 2 }, + { x: 1591448400000, y: 2 }, + { x: 1591449000000, y: 4 }, + { x: 1591449600000, y: 5 }, + { x: 1591450200000, y: 2 }, + { x: 1591450800000, y: 2 }, + { x: 1591451400000, y: 1 }, + { x: 1591452000000, y: 2 }, + ], + }, + ], +}; diff --git a/x-pack/plugins/observability/public/components/header/index.tsx b/x-pack/plugins/observability/public/components/header/index.tsx index 0e7417cd146c8..ffd55ce01b940 100644 --- a/x-pack/plugins/observability/public/components/header/index.tsx +++ b/x-pack/plugins/observability/public/components/header/index.tsx @@ -19,6 +19,7 @@ const Wrapper = styled.div<{ restrictWidth?: number }>` max-width: ${(props) => `${props.restrictWidth}px`}; margin: 0 auto; overflow: hidden; + padding: ${(props) => (props.restrictWidth ? 0 : '0 24px')}; `; interface Props { diff --git a/x-pack/plugins/observability/public/components/layout/with_header.tsx b/x-pack/plugins/observability/public/components/layout/with_header.tsx index ab86803b8434c..bc6d321f5b4ab 100644 --- a/x-pack/plugins/observability/public/components/layout/with_header.tsx +++ b/x-pack/plugins/observability/public/components/layout/with_header.tsx @@ -9,8 +9,12 @@ import styled from 'styled-components'; import { EuiPage, EuiPageBody, EuiSpacer, EuiPageProps } from '@elastic/eui'; import { Header } from '../header/index'; +const getPaddingSize = (props: EuiPageProps) => (props.restrictWidth ? 0 : '24px'); + const Page = styled(EuiPage)` background: transparent; + padding-right: ${getPaddingSize}; + padding-left: ${getPaddingSize}; `; const Container = styled.div<{ color?: string }>` diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 1ba90c2fb4a7b..12f5c5991a606 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -4,58 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ import { + AreaSeries, Axis, BarSeries, Chart, - Position, - ScaleType, - timeFormatter, - niceTimeFormatByDay, - Settings, - niceTimeFormatter, DARK_THEME, LIGHT_THEME, LineSeries, - AreaSeries, + niceTimeFormatter, + Position, + Settings, } from '@elastic/charts'; -import { - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, - EuiSwitch, -} from '@elastic/eui'; -import React, { useContext, useState } from 'react'; -import { ThemeContext } from 'styled-components'; +import { EuiDatePicker, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { groupBy } from 'lodash'; +import moment from 'moment'; +import React, { useContext, useState } from 'react'; +import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../../components/chart/container'; import { WithHeaderLayout } from '../../components/layout/with_header'; import { appsSection } from '../home/section'; import { ContinueJourney } from './continue_journey'; -import { series } from './logs.data'; -import { barData } from './uptime.data'; -import { apmData } from './apm.data'; +import { APMChart } from '../../components/chart/apm'; export const Overview = () => { const theme = useContext(ThemeContext); const [withAlert, setWithAlert] = useState(false); - // const start = series[0].time; - // const end = series[series.length - 1].time; - // const formatter = niceTimeFormatter([start, end]); - - // const startUptime = barData[0].x; - // const endUptime = barData[barData.length - 1].x; - // const formatterUptime = niceTimeFormatter([startUptime, endUptime]); - - const { transaction: transactions, error: errors } = groupBy(apmData, (d) => d.group); - - const startAPM = transactions[0].time; - const endAPM = transactions[transactions.length - 1].time; - const formatterAPM = niceTimeFormatter([startAPM, endAPM]); - const barSeriesColorAccessor = ({ specId, yAccessor, splitAccessors }: any) => { if (splitAccessors.get('group') === 'error') { return 'lightgray'; @@ -74,6 +49,12 @@ export const Overview = () => { headerColor={theme.eui.euiColorEmptyShade} bodyColor={theme.eui.euiPageBackgroundColor} > + + + {}} /> + + + { - + {/* { @@ -120,10 +101,10 @@ export const Overview = () => { data={apmData} /> - + */} - + {/* { @@ -159,63 +140,13 @@ export const Overview = () => { color={barSeriesColorAccessor2} /> - + */} - - - { - console.log('#### APM', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend={true} - legendPosition="bottom" - /> - - - - `${Number(d).toFixed(0)} %`} - groupId="errors" - /> - numeral(d).format('0a')} - /> - - + - + {/* { @@ -252,7 +183,7 @@ export const Overview = () => { color={barSeriesColorAccessor} /> - + */} @@ -262,191 +193,6 @@ export const Overview = () => { )} - {/* - - - - - - { - console.log('#### Logs', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - - - - - - - { - console.log('#### Metrics', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - - - - - - { - console.log('#### APM', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend={true} - legendPosition="bottom" - /> - - - - `${Number(d).toFixed(0)} %`} - groupId="errors" - /> - numeral(d).format('0a')} - /> - - - - - - - { - console.log('#### Uptime', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - - - - - - - {withAlert && ( - - - chart goes here - - - )} - */} From 711ca04367e73541529991b2a53aea9a64628580 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 15 Jun 2020 11:45:08 +0200 Subject: [PATCH 30/97] adding overview page --- .../observability/public/pages/home/index.tsx | 34 ---------------- .../public/pages/overview/index.tsx | 40 ++++++------------- 2 files changed, 13 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 17675db299913..9c64ca2a17016 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -128,40 +128,6 @@ export const Home = () => { - {/* Apps sections */} - - - - - - {appsSection.map((app) => ( - - } - title={ - -

{app.title}

-
- } - description={app.description} - /> -
- ))} -
-
- - - -
-
{/* Try it out */} diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 12f5c5991a606..7a0fd691ede34 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -3,46 +3,32 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { - AreaSeries, - Axis, - BarSeries, - Chart, - DARK_THEME, - LIGHT_THEME, - LineSeries, - niceTimeFormatter, - Position, - Settings, -} from '@elastic/charts'; import { EuiDatePicker, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui'; -import numeral from '@elastic/numeral'; -import { groupBy } from 'lodash'; import moment from 'moment'; import React, { useContext, useState } from 'react'; import { ThemeContext } from 'styled-components'; +import { APMChart } from '../../components/chart/apm'; import { ChartContainer } from '../../components/chart/container'; import { WithHeaderLayout } from '../../components/layout/with_header'; import { appsSection } from '../home/section'; import { ContinueJourney } from './continue_journey'; -import { APMChart } from '../../components/chart/apm'; export const Overview = () => { const theme = useContext(ThemeContext); const [withAlert, setWithAlert] = useState(false); - const barSeriesColorAccessor = ({ specId, yAccessor, splitAccessors }: any) => { - if (splitAccessors.get('group') === 'error') { - return 'lightgray'; - } - return 'red'; - }; - const barSeriesColorAccessor2 = ({ specId, yAccessor, splitAccessors }: any) => { - if (splitAccessors.get('group') === 'error') { - return '#CA8EAE'; - } - return '#9170B8'; - }; + // const barSeriesColorAccessor = ({ specId, yAccessor, splitAccessors }: any) => { + // if (splitAccessors.get('group') === 'error') { + // return 'lightgray'; + // } + // return 'red'; + // }; + // const barSeriesColorAccessor2 = ({ specId, yAccessor, splitAccessors }: any) => { + // if (splitAccessors.get('group') === 'error') { + // return '#CA8EAE'; + // } + // return '#9170B8'; + // }; return ( Date: Mon, 15 Jun 2020 15:59:05 +0200 Subject: [PATCH 31/97] adding metric charts --- .../public/components/chart/apm/apm.data.js | 1477 ----------------- .../public/components/chart/apm/index.tsx | 112 +- .../public/components/chart/apm/mock.data.ts | 352 ++-- .../public/components/chart/container.tsx | 50 +- .../public/components/chart/metrics/index.tsx | 52 + .../components/chart/metrics/mock.data.ts | 113 ++ .../public/pages/overview/index.tsx | 39 +- .../public/typings/data_handler/index.d.ts | 2 +- 8 files changed, 423 insertions(+), 1774 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/chart/apm/apm.data.js create mode 100644 x-pack/plugins/observability/public/components/chart/metrics/index.tsx create mode 100644 x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts diff --git a/x-pack/plugins/observability/public/components/chart/apm/apm.data.js b/x-pack/plugins/observability/public/components/chart/apm/apm.data.js deleted file mode 100644 index 6c9d585e61b88..0000000000000 --- a/x-pack/plugins/observability/public/components/chart/apm/apm.data.js +++ /dev/null @@ -1,1477 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -const apmData = [ - { - time: 1591365600000, - value: 32, - group: 'transaction', - }, - { - time: 1591365600000, - value: 5, - group: 'error', - }, - { - time: 1591366200000, - value: 43, - group: 'transaction', - }, - { - time: 1591366200000, - value: 4, - group: 'error', - }, - { - time: 1591366800000, - value: 22, - group: 'transaction', - }, - { - time: 1591366800000, - value: 1, - group: 'error', - }, - { - time: 1591367400000, - value: 29, - group: 'transaction', - }, - { - time: 1591367400000, - value: 5, - group: 'error', - }, - { - time: 1591368000000, - value: 39, - group: 'transaction', - }, - { - time: 1591368000000, - value: 8, - group: 'error', - }, - { - time: 1591368600000, - value: 36, - group: 'transaction', - }, - { - time: 1591368600000, - value: 4, - group: 'error', - }, - { - time: 1591369200000, - value: 50, - group: 'transaction', - }, - { - time: 1591369200000, - value: 3, - group: 'error', - }, - { - time: 1591369800000, - value: 31, - group: 'transaction', - }, - { - time: 1591369800000, - value: 6, - group: 'error', - }, - { - time: 1591370400000, - value: 39, - group: 'transaction', - }, - { - time: 1591370400000, - value: 9, - group: 'error', - }, - { - time: 1591371000000, - value: 26, - group: 'transaction', - }, - { - time: 1591371000000, - value: 1, - group: 'error', - }, - { - time: 1591371600000, - value: 45, - group: 'transaction', - }, - { - time: 1591371600000, - value: 7, - group: 'error', - }, - { - time: 1591372200000, - value: 27, - group: 'transaction', - }, - { - time: 1591372200000, - value: 4, - group: 'error', - }, - { - time: 1591372800000, - value: 37, - group: 'transaction', - }, - { - time: 1591372800000, - value: 8, - group: 'error', - }, - { - time: 1591373400000, - value: 55, - group: 'transaction', - }, - { - time: 1591373400000, - value: 6, - group: 'error', - }, - { - time: 1591374000000, - value: 31, - group: 'transaction', - }, - { - time: 1591374000000, - value: 3, - group: 'error', - }, - { - time: 1591374600000, - value: 26, - group: 'transaction', - }, - { - time: 1591374600000, - value: 5, - group: 'error', - }, - { - time: 1591375200000, - value: 57, - group: 'transaction', - }, - { - time: 1591375200000, - value: 7, - group: 'error', - }, - { - time: 1591375800000, - value: 25, - group: 'transaction', - }, - { - time: 1591375800000, - value: 5, - group: 'error', - }, - { - time: 1591376400000, - value: 28, - group: 'transaction', - }, - { - time: 1591376400000, - value: 6, - group: 'error', - }, - { - time: 1591377000000, - value: 40, - group: 'transaction', - }, - { - time: 1591377000000, - value: 4, - group: 'error', - }, - { - time: 1591377600000, - value: 33, - group: 'transaction', - }, - { - time: 1591377600000, - value: 4, - group: 'error', - }, - { - time: 1591378200000, - value: 33, - group: 'transaction', - }, - { - time: 1591378200000, - value: 7, - group: 'error', - }, - { - time: 1591378800000, - value: 31, - group: 'transaction', - }, - { - time: 1591378800000, - value: 5, - group: 'error', - }, - { - time: 1591379400000, - value: 32, - group: 'transaction', - }, - { - time: 1591379400000, - value: 7, - group: 'error', - }, - { - time: 1591380000000, - value: 34, - group: 'transaction', - }, - { - time: 1591380000000, - value: 6, - group: 'error', - }, - { - time: 1591380600000, - value: 31, - group: 'transaction', - }, - { - time: 1591380600000, - value: 3, - group: 'error', - }, - { - time: 1591381200000, - value: 16, - group: 'transaction', - }, - { - time: 1591381200000, - value: 4, - group: 'error', - }, - { - time: 1591381800000, - value: 34, - group: 'transaction', - }, - { - time: 1591381800000, - value: 5, - group: 'error', - }, - { - time: 1591382400000, - value: 33, - group: 'transaction', - }, - { - time: 1591382400000, - value: 4, - group: 'error', - }, - { - time: 1591383000000, - value: 35, - group: 'transaction', - }, - { - time: 1591383000000, - value: 8, - group: 'error', - }, - { - time: 1591383600000, - value: 47, - group: 'transaction', - }, - { - time: 1591383600000, - value: 5, - group: 'error', - }, - { - time: 1591384200000, - value: 44, - group: 'transaction', - }, - { - time: 1591384200000, - value: 5, - group: 'error', - }, - { - time: 1591384800000, - value: 21, - group: 'transaction', - }, - { - time: 1591384800000, - value: 4, - group: 'error', - }, - { - time: 1591385400000, - value: 25, - group: 'transaction', - }, - { - time: 1591385400000, - value: 5, - group: 'error', - }, - { - time: 1591386000000, - value: 34, - group: 'transaction', - }, - { - time: 1591386000000, - value: 5, - group: 'error', - }, - { - time: 1591386600000, - value: 37, - group: 'transaction', - }, - { - time: 1591386600000, - value: 7, - group: 'error', - }, - { - time: 1591387200000, - value: 38, - group: 'transaction', - }, - { - time: 1591387200000, - value: 5, - group: 'error', - }, - { - time: 1591387800000, - value: 28, - group: 'transaction', - }, - { - time: 1591387800000, - value: 6, - group: 'error', - }, - { - time: 1591388400000, - value: 32, - group: 'transaction', - }, - { - time: 1591388400000, - value: 6, - group: 'error', - }, - { - time: 1591389000000, - value: 37, - group: 'transaction', - }, - { - time: 1591389000000, - value: 4, - group: 'error', - }, - { - time: 1591389600000, - value: 25, - group: 'transaction', - }, - { - time: 1591389600000, - value: 1, - group: 'error', - }, - { - time: 1591390200000, - value: 33, - group: 'transaction', - }, - { - time: 1591390200000, - value: 6, - group: 'error', - }, - { - time: 1591390800000, - value: 34, - group: 'transaction', - }, - { - time: 1591390800000, - value: 7, - group: 'error', - }, - { - time: 1591391400000, - value: 30, - group: 'transaction', - }, - { - time: 1591391400000, - value: 5, - group: 'error', - }, - { - time: 1591392000000, - value: 45, - group: 'transaction', - }, - { - time: 1591392000000, - value: 6, - group: 'error', - }, - { - time: 1591392600000, - value: 42, - group: 'transaction', - }, - { - time: 1591392600000, - value: 5, - group: 'error', - }, - { - time: 1591393200000, - value: 23, - group: 'transaction', - }, - { - time: 1591393200000, - value: 3, - group: 'error', - }, - { - time: 1591393800000, - value: 33, - group: 'transaction', - }, - { - time: 1591393800000, - value: 3, - group: 'error', - }, - { - time: 1591394400000, - value: 38, - group: 'transaction', - }, - { - time: 1591394400000, - value: 4, - group: 'error', - }, - { - time: 1591395000000, - value: 30, - group: 'transaction', - }, - { - time: 1591395000000, - value: 2, - group: 'error', - }, - { - time: 1591395600000, - value: 25, - group: 'transaction', - }, - { - time: 1591395600000, - value: 4, - group: 'error', - }, - { - time: 1591396200000, - value: 33, - group: 'transaction', - }, - { - time: 1591396200000, - value: 3, - group: 'error', - }, - { - time: 1591396800000, - value: 37, - group: 'transaction', - }, - { - time: 1591396800000, - value: 8, - group: 'error', - }, - { - time: 1591397400000, - value: 43, - group: 'transaction', - }, - { - time: 1591397400000, - value: 5, - group: 'error', - }, - { - time: 1591398000000, - value: 30, - group: 'transaction', - }, - { - time: 1591398000000, - value: 4, - group: 'error', - }, - { - time: 1591398600000, - value: 36, - group: 'transaction', - }, - { - time: 1591398600000, - value: 4, - group: 'error', - }, - { - time: 1591399200000, - value: 28, - group: 'transaction', - }, - { - time: 1591399200000, - value: 3, - group: 'error', - }, - { - time: 1591399800000, - value: 39, - group: 'transaction', - }, - { - time: 1591399800000, - value: 7, - group: 'error', - }, - { - time: 1591400400000, - value: 27, - group: 'transaction', - }, - { - time: 1591400400000, - value: 4, - group: 'error', - }, - { - time: 1591401000000, - value: 41, - group: 'transaction', - }, - { - time: 1591401000000, - value: 4, - group: 'error', - }, - { - time: 1591401600000, - value: 25, - group: 'transaction', - }, - { - time: 1591401600000, - value: 3, - group: 'error', - }, - { - time: 1591402200000, - value: 31, - group: 'transaction', - }, - { - time: 1591402200000, - value: 3, - group: 'error', - }, - { - time: 1591402800000, - value: 28, - group: 'transaction', - }, - { - time: 1591402800000, - value: 1, - group: 'error', - }, - { - time: 1591403400000, - value: 29, - group: 'transaction', - }, - { - time: 1591403400000, - value: 5, - group: 'error', - }, - { - time: 1591404000000, - value: 49, - group: 'transaction', - }, - { - time: 1591404000000, - value: 7, - group: 'error', - }, - { - time: 1591404600000, - value: 24, - group: 'transaction', - }, - { - time: 1591404600000, - value: 5, - group: 'error', - }, - { - time: 1591405200000, - value: 41, - group: 'transaction', - }, - { - time: 1591405200000, - value: 6, - group: 'error', - }, - { - time: 1591405800000, - value: 30, - group: 'transaction', - }, - { - time: 1591405800000, - value: 3, - group: 'error', - }, - { - time: 1591406400000, - value: 36, - group: 'transaction', - }, - { - time: 1591406400000, - value: 5, - group: 'error', - }, - { - time: 1591407000000, - value: 39, - group: 'transaction', - }, - { - time: 1591407000000, - value: 5, - group: 'error', - }, - { - time: 1591407600000, - value: 23, - group: 'transaction', - }, - { - time: 1591407600000, - value: 0, - group: 'error', - }, - { - time: 1591408200000, - value: 40, - group: 'transaction', - }, - { - time: 1591408200000, - value: 4, - group: 'error', - }, - { - time: 1591408800000, - value: 34, - group: 'transaction', - }, - { - time: 1591408800000, - value: 9, - group: 'error', - }, - { - time: 1591409400000, - value: 28, - group: 'transaction', - }, - { - time: 1591409400000, - value: 5, - group: 'error', - }, - { - time: 1591410000000, - value: 33, - group: 'transaction', - }, - { - time: 1591410000000, - value: 6, - group: 'error', - }, - { - time: 1591410600000, - value: 31, - group: 'transaction', - }, - { - time: 1591410600000, - value: 4, - group: 'error', - }, - { - time: 1591411200000, - value: 39, - group: 'transaction', - }, - { - time: 1591411200000, - value: 5, - group: 'error', - }, - { - time: 1591411800000, - value: 33, - group: 'transaction', - }, - { - time: 1591411800000, - value: 5, - group: 'error', - }, - { - time: 1591412400000, - value: 35, - group: 'transaction', - }, - { - time: 1591412400000, - value: 2, - group: 'error', - }, - { - time: 1591413000000, - value: 31, - group: 'transaction', - }, - { - time: 1591413000000, - value: 4, - group: 'error', - }, - { - time: 1591413600000, - value: 35, - group: 'transaction', - }, - { - time: 1591413600000, - value: 8, - group: 'error', - }, - { - time: 1591414200000, - value: 37, - group: 'transaction', - }, - { - time: 1591414200000, - value: 5, - group: 'error', - }, - { - time: 1591414800000, - value: 26, - group: 'transaction', - }, - { - time: 1591414800000, - value: 4, - group: 'error', - }, - { - time: 1591415400000, - value: 27, - group: 'transaction', - }, - { - time: 1591415400000, - value: 4, - group: 'error', - }, - { - time: 1591416000000, - value: 26, - group: 'transaction', - }, - { - time: 1591416000000, - value: 3, - group: 'error', - }, - { - time: 1591416600000, - value: 34, - group: 'transaction', - }, - { - time: 1591416600000, - value: 7, - group: 'error', - }, - { - time: 1591417200000, - value: 33, - group: 'transaction', - }, - { - time: 1591417200000, - value: 7, - group: 'error', - }, - { - time: 1591417800000, - value: 38, - group: 'transaction', - }, - { - time: 1591417800000, - value: 6, - group: 'error', - }, - { - time: 1591418400000, - value: 34, - group: 'transaction', - }, - { - time: 1591418400000, - value: 5, - group: 'error', - }, - { - time: 1591419000000, - value: 37, - group: 'transaction', - }, - { - time: 1591419000000, - value: 5, - group: 'error', - }, - { - time: 1591419600000, - value: 24, - group: 'transaction', - }, - { - time: 1591419600000, - value: 7, - group: 'error', - }, - { - time: 1591420200000, - value: 25, - group: 'transaction', - }, - { - time: 1591420200000, - value: 5, - group: 'error', - }, - { - time: 1591420800000, - value: 20, - group: 'transaction', - }, - { - time: 1591420800000, - value: 2, - group: 'error', - }, - { - time: 1591421400000, - value: 35, - group: 'transaction', - }, - { - time: 1591421400000, - value: 3, - group: 'error', - }, - { - time: 1591422000000, - value: 41, - group: 'transaction', - }, - { - time: 1591422000000, - value: 8, - group: 'error', - }, - { - time: 1591422600000, - value: 40, - group: 'transaction', - }, - { - time: 1591422600000, - value: 5, - group: 'error', - }, - { - time: 1591423200000, - value: 33, - group: 'transaction', - }, - { - time: 1591423200000, - value: 5, - group: 'error', - }, - { - time: 1591423800000, - value: 24, - group: 'transaction', - }, - { - time: 1591423800000, - value: 1, - group: 'error', - }, - { - time: 1591424400000, - value: 44, - group: 'transaction', - }, - { - time: 1591424400000, - value: 3, - group: 'error', - }, - { - time: 1591425000000, - value: 24, - group: 'transaction', - }, - { - time: 1591425000000, - value: 5, - group: 'error', - }, - { - time: 1591425600000, - value: 32, - group: 'transaction', - }, - { - time: 1591425600000, - value: 4, - group: 'error', - }, - { - time: 1591426200000, - value: 37, - group: 'transaction', - }, - { - time: 1591426200000, - value: 8, - group: 'error', - }, - { - time: 1591426800000, - value: 34, - group: 'transaction', - }, - { - time: 1591426800000, - value: 8, - group: 'error', - }, - { - time: 1591427400000, - value: 28, - group: 'transaction', - }, - { - time: 1591427400000, - value: 5, - group: 'error', - }, - { - time: 1591428000000, - value: 26, - group: 'transaction', - }, - { - time: 1591428000000, - value: 3, - group: 'error', - }, - { - time: 1591428600000, - value: 37, - group: 'transaction', - }, - { - time: 1591428600000, - value: 4, - group: 'error', - }, - { - time: 1591429200000, - value: 36, - group: 'transaction', - }, - { - time: 1591429200000, - value: 3, - group: 'error', - }, - { - time: 1591429800000, - value: 37, - group: 'transaction', - }, - { - time: 1591429800000, - value: 5, - group: 'error', - }, - { - time: 1591430400000, - value: 23, - group: 'transaction', - }, - { - time: 1591430400000, - value: 3, - group: 'error', - }, - { - time: 1591431000000, - value: 47, - group: 'transaction', - }, - { - time: 1591431000000, - value: 4, - group: 'error', - }, - { - time: 1591431600000, - value: 41, - group: 'transaction', - }, - { - time: 1591431600000, - value: 4, - group: 'error', - }, - { - time: 1591432200000, - value: 24, - group: 'transaction', - }, - { - time: 1591432200000, - value: 5, - group: 'error', - }, - { - time: 1591432800000, - value: 34, - group: 'transaction', - }, - { - time: 1591432800000, - value: 1, - group: 'error', - }, - { - time: 1591433400000, - value: 27, - group: 'transaction', - }, - { - time: 1591433400000, - value: 4, - group: 'error', - }, - { - time: 1591434000000, - value: 34, - group: 'transaction', - }, - { - time: 1591434000000, - value: 4, - group: 'error', - }, - { - time: 1591434600000, - value: 44, - group: 'transaction', - }, - { - time: 1591434600000, - value: 9, - group: 'error', - }, - { - time: 1591435200000, - value: 20, - group: 'transaction', - }, - { - time: 1591435200000, - value: 4, - group: 'error', - }, - { - time: 1591435800000, - value: 34, - group: 'transaction', - }, - { - time: 1591435800000, - value: 4, - group: 'error', - }, - { - time: 1591436400000, - value: 29, - group: 'transaction', - }, - { - time: 1591436400000, - value: 3, - group: 'error', - }, - { - time: 1591437000000, - value: 28, - group: 'transaction', - }, - { - time: 1591437000000, - value: 6, - group: 'error', - }, - { - time: 1591437600000, - value: 36, - group: 'transaction', - }, - { - time: 1591437600000, - value: 1, - group: 'error', - }, - { - time: 1591438200000, - value: 34, - group: 'transaction', - }, - { - time: 1591438200000, - value: 5, - group: 'error', - }, - { - time: 1591438800000, - value: 26, - group: 'transaction', - }, - { - time: 1591438800000, - value: 4, - group: 'error', - }, - { - time: 1591439400000, - value: 29, - group: 'transaction', - }, - { - time: 1591439400000, - value: 4, - group: 'error', - }, - { - time: 1591440000000, - value: 45, - group: 'transaction', - }, - { - time: 1591440000000, - value: 7, - group: 'error', - }, - { - time: 1591440600000, - value: 34, - group: 'transaction', - }, - { - time: 1591440600000, - value: 5, - group: 'error', - }, - { - time: 1591441200000, - value: 25, - group: 'transaction', - }, - { - time: 1591441200000, - value: 6, - group: 'error', - }, - { - time: 1591441800000, - value: 34, - group: 'transaction', - }, - { - time: 1591441800000, - value: 6, - group: 'error', - }, - { - time: 1591442400000, - value: 28, - group: 'transaction', - }, - { - time: 1591442400000, - value: 4, - group: 'error', - }, - { - time: 1591443000000, - value: 34, - group: 'transaction', - }, - { - time: 1591443000000, - value: 5, - group: 'error', - }, - { - time: 1591443600000, - value: 31, - group: 'transaction', - }, - { - time: 1591443600000, - value: 3, - group: 'error', - }, - { - time: 1591444200000, - value: 24, - group: 'transaction', - }, - { - time: 1591444200000, - value: 3, - group: 'error', - }, - { - time: 1591444800000, - value: 34, - group: 'transaction', - }, - { - time: 1591444800000, - value: 4, - group: 'error', - }, - { - time: 1591445400000, - value: 21, - group: 'transaction', - }, - { - time: 1591445400000, - value: 2, - group: 'error', - }, - { - time: 1591446000000, - value: 40, - group: 'transaction', - }, - { - time: 1591446000000, - value: 6, - group: 'error', - }, - { - time: 1591446600000, - value: 37, - group: 'transaction', - }, - { - time: 1591446600000, - value: 4, - group: 'error', - }, - { - time: 1591447200000, - value: 31, - group: 'transaction', - }, - { - time: 1591447200000, - value: 5, - group: 'error', - }, - { - time: 1591447800000, - value: 21, - group: 'transaction', - }, - { - time: 1591447800000, - value: 2, - group: 'error', - }, - { - time: 1591448400000, - value: 24, - group: 'transaction', - }, - { - time: 1591448400000, - value: 2, - group: 'error', - }, - { - time: 1591449000000, - value: 30, - group: 'transaction', - }, - { - time: 1591449000000, - value: 4, - group: 'error', - }, - { - time: 1591449600000, - value: 22, - group: 'transaction', - }, - { - time: 1591449600000, - value: 5, - group: 'error', - }, - { - time: 1591450200000, - value: 27, - group: 'transaction', - }, - { - time: 1591450200000, - value: 2, - group: 'error', - }, - { - time: 1591450800000, - value: 30, - group: 'transaction', - }, - { - time: 1591450800000, - value: 2, - group: 'error', - }, - { - time: 1591451400000, - value: 22, - group: 'transaction', - }, - { - time: 1591451400000, - value: 1, - group: 'error', - }, - { - time: 1591452000000, - value: 9, - group: 'transaction', - }, - { - time: 1591452000000, - value: 2, - group: 'error', - }, -]; - -const _ = require('lodash'); - -const grouped = _.groupBy(apmData, 'group'); -console.log('### caue: grouped', grouped); - -const series = Object.keys(grouped).map((key) => { - const data = grouped[key]; - const coordinates = data.map((d) => ({ x: d.time, y: d.value })); - return { - key, - label: key, - coordinates, - }; -}); - -console.log('### caue: series', JSON.stringify(series)); - -// console.log('### caue: coordinates', coordinates); diff --git a/x-pack/plugins/observability/public/components/chart/apm/index.tsx b/x-pack/plugins/observability/public/components/chart/apm/index.tsx index f7aa9f94fa21f..de5471ce0e9a7 100644 --- a/x-pack/plugins/observability/public/components/chart/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/apm/index.tsx @@ -4,36 +4,46 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; import { - Chart, - Settings, - BarSeries, - LineSeries, Axis, - Position, + BarSeries, + Chart, DARK_THEME, LIGHT_THEME, + LineSeries, niceTimeFormatter, + Position, + Settings, } from '@elastic/charts'; -import { ThemeContext } from 'styled-components'; +import { EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { data } from './mock.data'; +import { apmData as data } from './mock.data'; export const APMChart = () => { const theme = useContext(ThemeContext); - const transactionSeries = data.series.find((d) => d.key === 'transaction')!; - const errorSeries = data.series.find((d) => d.key === 'error')!; + const transactionSeries = data.series.find((d) => d.key === 'transactions'); + const errorSeries = data.series.find((d) => d.key === 'errors'); - const startAPM = transactionSeries.coordinates[0].x; - const endAPM = transactionSeries.coordinates[transactionSeries.coordinates.length - 1].x; + const startAPM = transactionSeries?.coordinates[0].x || 0; + const endAPM = transactionSeries?.coordinates[transactionSeries?.coordinates.length - 1].x || 0; const formatterAPM = niceTimeFormatter([startAPM, endAPM]); + const getSerieColor = (color?: string) => { + if (color) { + return theme.eui[color]; + } + }; + return ( - - + + {data.stats.map((stat) => ( + + ))} + { console.log('#### APM', x); @@ -42,40 +52,48 @@ export const APMChart = () => { showLegend={true} legendPosition="bottom" /> - - + {transactionSeries?.coordinates && ( + <> + + numeral(d).format('0a')} + /> + + )} + {errorSeries?.coordinates && ( + <> + + `${Number(d).toFixed(0)} %`} + groupId="errors" + /> + + )} - `${Number(d).toFixed(0)} %`} - groupId="errors" - /> - numeral(d).format('0a')} - /> ); diff --git a/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts b/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts index 486e07dc787d9..84b5eb249cc7a 100644 --- a/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts +++ b/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts @@ -4,43 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -interface Stat { - label: string; - value: number; - color?: string; -} +import { i18n } from '@kbn/i18n'; +import { FetchDataResponse } from '../../../typings/data_handler'; -interface Coordinates { - x: number; - y: number; -} - -interface Series { - label: string; - coordinates: Coordinates[]; - color?: string; - key?: string; -} - -interface FetcherResponse { - title: string; - appLink: string; - stats: Stat[]; - series: Series[]; -} - -export const data: FetcherResponse = { - title: 'Apm', - appLink: '', - stats: [ - { label: 'Services', value: 14 }, - { label: 'Transactions', value: 312000 }, - { label: 'Error rate', value: 0.14 }, - ], +export const apmData: FetchDataResponse = { + title: i18n.translate('apm.observabilityDashboard.title', { + defaultMessage: 'APM', + }), + appLink: '/app/apm', + stats: [{ label: 'Services', value: '11' }], series: [ { - key: 'transaction', - label: 'transaction', + key: 'transactions', + label: i18n.translate('apm.observabilityDashboard.chart.transactions', { + defaultMessage: 'Transactions', + }), + color: 'euiColorVis1', coordinates: [ { x: 1591365600000, y: 32 }, { x: 1591366200000, y: 43 }, @@ -189,156 +168,159 @@ export const data: FetcherResponse = { { x: 1591452000000, y: 9 }, ], }, - { - key: 'error', - label: 'error', - coordinates: [ - { x: 1591365600000, y: 5 }, - { x: 1591366200000, y: 4 }, - { x: 1591366800000, y: 1 }, - { x: 1591367400000, y: 5 }, - { x: 1591368000000, y: 8 }, - { x: 1591368600000, y: 4 }, - { x: 1591369200000, y: 3 }, - { x: 1591369800000, y: 6 }, - { x: 1591370400000, y: 9 }, - { x: 1591371000000, y: 1 }, - { x: 1591371600000, y: 7 }, - { x: 1591372200000, y: 4 }, - { x: 1591372800000, y: 8 }, - { x: 1591373400000, y: 6 }, - { x: 1591374000000, y: 3 }, - { x: 1591374600000, y: 5 }, - { x: 1591375200000, y: 7 }, - { x: 1591375800000, y: 5 }, - { x: 1591376400000, y: 6 }, - { x: 1591377000000, y: 4 }, - { x: 1591377600000, y: 4 }, - { x: 1591378200000, y: 7 }, - { x: 1591378800000, y: 5 }, - { x: 1591379400000, y: 7 }, - { x: 1591380000000, y: 6 }, - { x: 1591380600000, y: 3 }, - { x: 1591381200000, y: 4 }, - { x: 1591381800000, y: 5 }, - { x: 1591382400000, y: 4 }, - { x: 1591383000000, y: 8 }, - { x: 1591383600000, y: 5 }, - { x: 1591384200000, y: 5 }, - { x: 1591384800000, y: 4 }, - { x: 1591385400000, y: 5 }, - { x: 1591386000000, y: 5 }, - { x: 1591386600000, y: 7 }, - { x: 1591387200000, y: 5 }, - { x: 1591387800000, y: 6 }, - { x: 1591388400000, y: 6 }, - { x: 1591389000000, y: 4 }, - { x: 1591389600000, y: 1 }, - { x: 1591390200000, y: 6 }, - { x: 1591390800000, y: 7 }, - { x: 1591391400000, y: 5 }, - { x: 1591392000000, y: 6 }, - { x: 1591392600000, y: 5 }, - { x: 1591393200000, y: 3 }, - { x: 1591393800000, y: 3 }, - { x: 1591394400000, y: 4 }, - { x: 1591395000000, y: 2 }, - { x: 1591395600000, y: 4 }, - { x: 1591396200000, y: 3 }, - { x: 1591396800000, y: 8 }, - { x: 1591397400000, y: 5 }, - { x: 1591398000000, y: 4 }, - { x: 1591398600000, y: 4 }, - { x: 1591399200000, y: 3 }, - { x: 1591399800000, y: 7 }, - { x: 1591400400000, y: 4 }, - { x: 1591401000000, y: 4 }, - { x: 1591401600000, y: 3 }, - { x: 1591402200000, y: 3 }, - { x: 1591402800000, y: 1 }, - { x: 1591403400000, y: 5 }, - { x: 1591404000000, y: 7 }, - { x: 1591404600000, y: 5 }, - { x: 1591405200000, y: 6 }, - { x: 1591405800000, y: 3 }, - { x: 1591406400000, y: 5 }, - { x: 1591407000000, y: 5 }, - { x: 1591407600000, y: 0 }, - { x: 1591408200000, y: 4 }, - { x: 1591408800000, y: 9 }, - { x: 1591409400000, y: 5 }, - { x: 1591410000000, y: 6 }, - { x: 1591410600000, y: 4 }, - { x: 1591411200000, y: 5 }, - { x: 1591411800000, y: 5 }, - { x: 1591412400000, y: 2 }, - { x: 1591413000000, y: 4 }, - { x: 1591413600000, y: 8 }, - { x: 1591414200000, y: 5 }, - { x: 1591414800000, y: 4 }, - { x: 1591415400000, y: 4 }, - { x: 1591416000000, y: 3 }, - { x: 1591416600000, y: 7 }, - { x: 1591417200000, y: 7 }, - { x: 1591417800000, y: 6 }, - { x: 1591418400000, y: 5 }, - { x: 1591419000000, y: 5 }, - { x: 1591419600000, y: 7 }, - { x: 1591420200000, y: 5 }, - { x: 1591420800000, y: 2 }, - { x: 1591421400000, y: 3 }, - { x: 1591422000000, y: 8 }, - { x: 1591422600000, y: 5 }, - { x: 1591423200000, y: 5 }, - { x: 1591423800000, y: 1 }, - { x: 1591424400000, y: 3 }, - { x: 1591425000000, y: 5 }, - { x: 1591425600000, y: 4 }, - { x: 1591426200000, y: 8 }, - { x: 1591426800000, y: 8 }, - { x: 1591427400000, y: 5 }, - { x: 1591428000000, y: 3 }, - { x: 1591428600000, y: 4 }, - { x: 1591429200000, y: 3 }, - { x: 1591429800000, y: 5 }, - { x: 1591430400000, y: 3 }, - { x: 1591431000000, y: 4 }, - { x: 1591431600000, y: 4 }, - { x: 1591432200000, y: 5 }, - { x: 1591432800000, y: 1 }, - { x: 1591433400000, y: 4 }, - { x: 1591434000000, y: 4 }, - { x: 1591434600000, y: 9 }, - { x: 1591435200000, y: 4 }, - { x: 1591435800000, y: 4 }, - { x: 1591436400000, y: 3 }, - { x: 1591437000000, y: 6 }, - { x: 1591437600000, y: 1 }, - { x: 1591438200000, y: 5 }, - { x: 1591438800000, y: 4 }, - { x: 1591439400000, y: 4 }, - { x: 1591440000000, y: 7 }, - { x: 1591440600000, y: 5 }, - { x: 1591441200000, y: 6 }, - { x: 1591441800000, y: 6 }, - { x: 1591442400000, y: 4 }, - { x: 1591443000000, y: 5 }, - { x: 1591443600000, y: 3 }, - { x: 1591444200000, y: 3 }, - { x: 1591444800000, y: 4 }, - { x: 1591445400000, y: 2 }, - { x: 1591446000000, y: 6 }, - { x: 1591446600000, y: 4 }, - { x: 1591447200000, y: 5 }, - { x: 1591447800000, y: 2 }, - { x: 1591448400000, y: 2 }, - { x: 1591449000000, y: 4 }, - { x: 1591449600000, y: 5 }, - { x: 1591450200000, y: 2 }, - { x: 1591450800000, y: 2 }, - { x: 1591451400000, y: 1 }, - { x: 1591452000000, y: 2 }, - ], - }, + // { + // key: 'errors', + // label: i18n.translate('apm.observabilityDashboard.chart.errors', { + // defaultMessage: 'Errors', + // }), + // color: 'euiColorVis5', + // coordinates: [ + // { x: 1591365600000, y: 5 }, + // { x: 1591366200000, y: 4 }, + // { x: 1591366800000, y: 1 }, + // { x: 1591367400000, y: 5 }, + // { x: 1591368000000, y: 8 }, + // { x: 1591368600000, y: 4 }, + // { x: 1591369200000, y: 3 }, + // { x: 1591369800000, y: 6 }, + // { x: 1591370400000, y: 9 }, + // { x: 1591371000000, y: 1 }, + // { x: 1591371600000, y: 7 }, + // { x: 1591372200000, y: 4 }, + // { x: 1591372800000, y: 8 }, + // { x: 1591373400000, y: 6 }, + // { x: 1591374000000, y: 3 }, + // { x: 1591374600000, y: 5 }, + // { x: 1591375200000, y: 7 }, + // { x: 1591375800000, y: 5 }, + // { x: 1591376400000, y: 6 }, + // { x: 1591377000000, y: 4 }, + // { x: 1591377600000, y: 4 }, + // { x: 1591378200000, y: 7 }, + // { x: 1591378800000, y: 5 }, + // { x: 1591379400000, y: 7 }, + // { x: 1591380000000, y: 6 }, + // { x: 1591380600000, y: 3 }, + // { x: 1591381200000, y: 4 }, + // { x: 1591381800000, y: 5 }, + // { x: 1591382400000, y: 4 }, + // { x: 1591383000000, y: 8 }, + // { x: 1591383600000, y: 5 }, + // { x: 1591384200000, y: 5 }, + // { x: 1591384800000, y: 4 }, + // { x: 1591385400000, y: 5 }, + // { x: 1591386000000, y: 5 }, + // { x: 1591386600000, y: 7 }, + // { x: 1591387200000, y: 5 }, + // { x: 1591387800000, y: 6 }, + // { x: 1591388400000, y: 6 }, + // { x: 1591389000000, y: 4 }, + // { x: 1591389600000, y: 1 }, + // { x: 1591390200000, y: 6 }, + // { x: 1591390800000, y: 7 }, + // { x: 1591391400000, y: 5 }, + // { x: 1591392000000, y: 6 }, + // { x: 1591392600000, y: 5 }, + // { x: 1591393200000, y: 3 }, + // { x: 1591393800000, y: 3 }, + // { x: 1591394400000, y: 4 }, + // { x: 1591395000000, y: 2 }, + // { x: 1591395600000, y: 4 }, + // { x: 1591396200000, y: 3 }, + // { x: 1591396800000, y: 8 }, + // { x: 1591397400000, y: 5 }, + // { x: 1591398000000, y: 4 }, + // { x: 1591398600000, y: 4 }, + // { x: 1591399200000, y: 3 }, + // { x: 1591399800000, y: 7 }, + // { x: 1591400400000, y: 4 }, + // { x: 1591401000000, y: 4 }, + // { x: 1591401600000, y: 3 }, + // { x: 1591402200000, y: 3 }, + // { x: 1591402800000, y: 1 }, + // { x: 1591403400000, y: 5 }, + // { x: 1591404000000, y: 7 }, + // { x: 1591404600000, y: 5 }, + // { x: 1591405200000, y: 6 }, + // { x: 1591405800000, y: 3 }, + // { x: 1591406400000, y: 5 }, + // { x: 1591407000000, y: 5 }, + // { x: 1591407600000, y: 0 }, + // { x: 1591408200000, y: 4 }, + // { x: 1591408800000, y: 9 }, + // { x: 1591409400000, y: 5 }, + // { x: 1591410000000, y: 6 }, + // { x: 1591410600000, y: 4 }, + // { x: 1591411200000, y: 5 }, + // { x: 1591411800000, y: 5 }, + // { x: 1591412400000, y: 2 }, + // { x: 1591413000000, y: 4 }, + // { x: 1591413600000, y: 8 }, + // { x: 1591414200000, y: 5 }, + // { x: 1591414800000, y: 4 }, + // { x: 1591415400000, y: 4 }, + // { x: 1591416000000, y: 3 }, + // { x: 1591416600000, y: 7 }, + // { x: 1591417200000, y: 7 }, + // { x: 1591417800000, y: 6 }, + // { x: 1591418400000, y: 5 }, + // { x: 1591419000000, y: 5 }, + // { x: 1591419600000, y: 7 }, + // { x: 1591420200000, y: 5 }, + // { x: 1591420800000, y: 2 }, + // { x: 1591421400000, y: 3 }, + // { x: 1591422000000, y: 8 }, + // { x: 1591422600000, y: 5 }, + // { x: 1591423200000, y: 5 }, + // { x: 1591423800000, y: 1 }, + // { x: 1591424400000, y: 3 }, + // { x: 1591425000000, y: 5 }, + // { x: 1591425600000, y: 4 }, + // { x: 1591426200000, y: 8 }, + // { x: 1591426800000, y: 8 }, + // { x: 1591427400000, y: 5 }, + // { x: 1591428000000, y: 3 }, + // { x: 1591428600000, y: 4 }, + // { x: 1591429200000, y: 3 }, + // { x: 1591429800000, y: 5 }, + // { x: 1591430400000, y: 3 }, + // { x: 1591431000000, y: 4 }, + // { x: 1591431600000, y: 4 }, + // { x: 1591432200000, y: 5 }, + // { x: 1591432800000, y: 1 }, + // { x: 1591433400000, y: 4 }, + // { x: 1591434000000, y: 4 }, + // { x: 1591434600000, y: 9 }, + // { x: 1591435200000, y: 4 }, + // { x: 1591435800000, y: 4 }, + // { x: 1591436400000, y: 3 }, + // { x: 1591437000000, y: 6 }, + // { x: 1591437600000, y: 1 }, + // { x: 1591438200000, y: 5 }, + // { x: 1591438800000, y: 4 }, + // { x: 1591439400000, y: 4 }, + // { x: 1591440000000, y: 7 }, + // { x: 1591440600000, y: 5 }, + // { x: 1591441200000, y: 6 }, + // { x: 1591441800000, y: 6 }, + // { x: 1591442400000, y: 4 }, + // { x: 1591443000000, y: 5 }, + // { x: 1591443600000, y: 3 }, + // { x: 1591444200000, y: 3 }, + // { x: 1591444800000, y: 4 }, + // { x: 1591445400000, y: 2 }, + // { x: 1591446000000, y: 6 }, + // { x: 1591446600000, y: 4 }, + // { x: 1591447200000, y: 5 }, + // { x: 1591447800000, y: 2 }, + // { x: 1591448400000, y: 2 }, + // { x: 1591449000000, y: 4 }, + // { x: 1591449600000, y: 5 }, + // { x: 1591450200000, y: 2 }, + // { x: 1591450800000, y: 2 }, + // { x: 1591451400000, y: 1 }, + // { x: 1591452000000, y: 2 }, + // ], + // }, ], }; diff --git a/x-pack/plugins/observability/public/components/chart/container.tsx b/x-pack/plugins/observability/public/components/chart/container.tsx index f034412e20738..5127317129087 100644 --- a/x-pack/plugins/observability/public/components/chart/container.tsx +++ b/x-pack/plugins/observability/public/components/chart/container.tsx @@ -3,40 +3,36 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiAccordion, EuiButtonEmpty, EuiPanel, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; -import styled from 'styled-components'; interface Props { title: string; + appLink?: string; children: React.ReactNode; } -const Container = styled.div` - .accordion-button { - width: 100%; - } -`; - -export const ChartContainer = ({ title, children }: Props) => { +export const ChartContainer = ({ title, appLink, children }: Props) => { return ( - - - -
{title}
-
- - - } - > - - {children} -
-
+ +
{title}
+ + } + extraAction={ + + {i18n.translate('xpack.observability.chart.viewInAppLabel', { + defaultMessage: 'View in app', + })} + + } + > + {children} +
); }; diff --git a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx new file mode 100644 index 0000000000000..3a8142f591017 --- /dev/null +++ b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; +import { ChartContainer } from '../container'; +import { data } from './mock.data'; + +export const MetricsChart = () => { + const theme = useContext(ThemeContext); + return ( + + + {data.stats.map((stat) => { + return ( + + + + ); + })} + {data.series.map((serie) => { + return ( + + + + + + + + + ); + })} + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts b/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts new file mode 100644 index 0000000000000..9b7251aa85f3c --- /dev/null +++ b/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FetchDataResponse } from '../../../typings/data_handler'; + +export const data: FetchDataResponse = { + title: i18n.translate('metrics.observabilityDashboard.title', { + defaultMessage: 'Metrics', + }), + appLink: '/app/apm', + stats: [ + { label: 'Hosts', value: '11' }, + { label: 'CPU usage', value: '80' }, + { label: 'Memory Usage', value: '36.2' }, + { label: 'Disk Usage', value: '32.4' }, + ], + series: [ + { + label: 'Outbount trafic', + coordinates: [ + { + x: 1589805437549, + y: 331514, + }, + { + x: 1590047357549, + y: 319208, + }, + { + x: 1590289277549, + y: 309648, + }, + { + x: 1590531197549, + y: 280568, + }, + { + x: 1590773117549, + y: 337180, + }, + { + x: 1591015037549, + y: 122468, + }, + { + x: 1591256957549, + y: 184164, + }, + { + x: 1591498877549, + y: 316323, + }, + { + x: 1591740797549, + y: 307351, + }, + { + x: 1591982717549, + y: 290262, + }, + ], + }, + { + label: 'Inbound trafic', + coordinates: [ + { + x: 1589805437549, + y: 331514, + }, + { + x: 1590047357549, + y: 319208, + }, + { + x: 1590289277549, + y: 309648, + }, + { + x: 1590531197549, + y: 280568, + }, + { + x: 1590773117549, + y: 337180, + }, + { + x: 1591015037549, + y: 122468, + }, + { + x: 1591256957549, + y: 184164, + }, + { + x: 1591498877549, + y: 316323, + }, + { + x: 1591740797549, + y: 307351, + }, + { + x: 1591982717549, + y: 290262, + }, + ], + }, + ], +}; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 7a0fd691ede34..b4da4aa546e18 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -12,6 +12,7 @@ import { ChartContainer } from '../../components/chart/container'; import { WithHeaderLayout } from '../../components/layout/with_header'; import { appsSection } from '../home/section'; import { ContinueJourney } from './continue_journey'; +import { MetricsChart } from '../../components/chart/metrics'; export const Overview = () => { const theme = useContext(ThemeContext); @@ -90,43 +91,7 @@ export const Overview = () => {
*/}
- {/* - - { - console.log('#### Metrics', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - */} + diff --git a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts b/x-pack/plugins/observability/public/typings/data_handler/index.d.ts index a208e4e7c223d..5374beb41bd70 100644 --- a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts +++ b/x-pack/plugins/observability/public/typings/data_handler/index.d.ts @@ -22,7 +22,7 @@ interface Series { key?: string; } -interface FetchDataResponse { +export interface FetchDataResponse { title: string; appLink: string; stats: Stat[]; From 3272a6974b4a191245e1c9bcd829eea4e18f9db2 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 15 Jun 2020 19:06:56 +0200 Subject: [PATCH 32/97] adding charts --- .../public/components/chart/apm/mock.data.ts | 2 +- .../public/components/chart/metrics/index.tsx | 6 +- .../components/chart/metrics/mock.data.ts | 8 +- .../components/chart/stacked_bar/index.tsx | 137 +++++++ .../public/components/stats/index.tsx | 31 ++ .../public/pages/overview/index.tsx | 93 +---- .../public/pages/overview/logs.data.ts | 333 ------------------ .../public/pages/overview/logs.mock.ts | 322 +++++++++++++++++ .../public/pages/overview/uptime.data.ts | 168 --------- .../public/pages/overview/uptime.mock.ts | 163 +++++++++ .../public/typings/data_handler/index.d.ts | 4 +- 11 files changed, 669 insertions(+), 598 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx create mode 100644 x-pack/plugins/observability/public/components/stats/index.tsx delete mode 100644 x-pack/plugins/observability/public/pages/overview/logs.data.ts create mode 100644 x-pack/plugins/observability/public/pages/overview/logs.mock.ts delete mode 100644 x-pack/plugins/observability/public/pages/overview/uptime.data.ts create mode 100644 x-pack/plugins/observability/public/pages/overview/uptime.mock.ts diff --git a/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts b/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts index 84b5eb249cc7a..8541b3f24695c 100644 --- a/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts +++ b/x-pack/plugins/observability/public/components/chart/apm/mock.data.ts @@ -12,7 +12,7 @@ export const apmData: FetchDataResponse = { defaultMessage: 'APM', }), appLink: '/app/apm', - stats: [{ label: 'Services', value: '11' }], + stats: [{ label: 'Services', value: 11 }], series: [ { key: 'transactions', diff --git a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx index 3a8142f591017..dfd0891a04cca 100644 --- a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx @@ -5,7 +5,7 @@ */ import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; @@ -19,7 +19,9 @@ export const MetricsChart = () => { {data.stats.map((stat) => { return ( - + + + ); })} diff --git a/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts b/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts index 9b7251aa85f3c..e06d76769da1f 100644 --- a/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts +++ b/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts @@ -13,10 +13,10 @@ export const data: FetchDataResponse = { }), appLink: '/app/apm', stats: [ - { label: 'Hosts', value: '11' }, - { label: 'CPU usage', value: '80' }, - { label: 'Memory Usage', value: '36.2' }, - { label: 'Disk Usage', value: '32.4' }, + { label: 'Hosts', value: 11 }, + { label: 'CPU usage', value: 80 }, + { label: 'Memory Usage', value: 36.2 }, + { label: 'Disk Usage', value: 32.4 }, ], series: [ { diff --git a/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx b/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx new file mode 100644 index 0000000000000..c05e16a544a97 --- /dev/null +++ b/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useContext, Fragment } from 'react'; +import { + Chart, + Settings, + DARK_THEME, + LIGHT_THEME, + BarSeries, + ScaleType, + Axis, + Position, + niceTimeFormatter, +} from '@elastic/charts'; +import { ThemeContext } from 'styled-components'; +import { euiPaletteColorBlind } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { FetchDataResponse } from '../../../typings/data_handler'; +import { ChartContainer } from '../container'; +import { Stats } from '../../stats'; + +interface Props { + data?: FetchDataResponse; +} + +export const StackedBarChart = ({ data }: Props) => { + const theme = useContext(ThemeContext); + + if (!data) { + return null; + } + + const customColors = { + colors: { + vizColors: euiPaletteColorBlind({ rotations: Math.ceil(data.series.length / 10) }), + }, + }; + + const min = data.series[0]?.coordinates[0].x || 0; + const max = data.series[0]?.coordinates[data.series[0]?.coordinates.length - 1].x || 0; + const formatter = niceTimeFormatter([min, max]); + + const getSerieColor = (color?: string) => { + if (color) { + return theme.eui[color]; + } + }; + + return ( + + numeral(value).format('0a')} /> + + { + console.log('#### Logs', x); + }} + theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} + showLegend + legendPosition="bottom" + xDomain={{ min, max }} + /> + {data.series.map((serie) => { + return ( + + + + numeral(d).format('0a')} + /> + + ); + })} + + + ); +}; + +{ + /* + + { + console.log('#### Logs', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min: startAPM, max: endAPM }} + /> + + numeral(d).format('0a')} + /> + + + + */ +} diff --git a/x-pack/plugins/observability/public/components/stats/index.tsx b/x-pack/plugins/observability/public/components/stats/index.tsx new file mode 100644 index 0000000000000..22d4a1591e538 --- /dev/null +++ b/x-pack/plugins/observability/public/components/stats/index.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; +import React from 'react'; +import { Stat } from '../../typings/data_handler'; + +interface Props { + stats: Stat[]; + formatter?: (value: number) => string; +} + +export const Stats = ({ stats, formatter }: Props) => { + return ( + + {stats.map((stat) => { + return ( + + + + ); + })} + + ); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index b4da4aa546e18..f2f60c194ba13 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -13,24 +13,14 @@ import { WithHeaderLayout } from '../../components/layout/with_header'; import { appsSection } from '../home/section'; import { ContinueJourney } from './continue_journey'; import { MetricsChart } from '../../components/chart/metrics'; +import { StackedBarChart } from '../../components/chart/stacked_bar'; +import { logsData } from './logs.mock'; +import { uptimeData } from './uptime.mock'; export const Overview = () => { const theme = useContext(ThemeContext); const [withAlert, setWithAlert] = useState(false); - // const barSeriesColorAccessor = ({ specId, yAccessor, splitAccessors }: any) => { - // if (splitAccessors.get('group') === 'error') { - // return 'lightgray'; - // } - // return 'red'; - // }; - // const barSeriesColorAccessor2 = ({ specId, yAccessor, splitAccessors }: any) => { - // if (splitAccessors.get('group') === 'error') { - // return '#CA8EAE'; - // } - // return '#9170B8'; - // }; - return ( { - {/* - - { - console.log('#### Logs', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - - */} + @@ -97,44 +51,7 @@ export const Overview = () => { - {/* - - { - console.log('#### Uptime', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - - */} + diff --git a/x-pack/plugins/observability/public/pages/overview/logs.data.ts b/x-pack/plugins/observability/public/pages/overview/logs.data.ts deleted file mode 100644 index 3102a309d29aa..0000000000000 --- a/x-pack/plugins/observability/public/pages/overview/logs.data.ts +++ /dev/null @@ -1,333 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const series = [ - { - group: 'unknown', - time: 1588942800000, - value: 7382.380952380952, - }, - { - group: 'kibana.log', - time: 1588942800000, - value: 358.3333333333333, - }, - { - group: 'nginx.access', - time: 1588942800000, - value: 594.3174603174604, - }, - { - group: 'nginx.error', - time: 1588942800000, - value: 134.79365079365078, - }, - { - group: 'postgresql.log', - time: 1588942800000, - value: 0.1935483870967742, - }, - { - group: 'redis.log', - time: 1588942800000, - value: 11.714285714285714, - }, - { - group: 'redis.slowlog', - time: 1588942800000, - value: 11.714285714285714, - }, - { - group: 'unknown', - time: 1589078700000, - value: 7147.549668874172, - }, - { - group: 'kibana.log', - time: 1589078700000, - value: 321.26490066225165, - }, - { - group: 'nginx.access', - time: 1589078700000, - value: 637.9072847682119, - }, - { - group: 'nginx.error', - time: 1589078700000, - value: 135.9205298013245, - }, - { - group: 'postgresql.log', - time: 1589078700000, - value: 0.4370860927152318, - }, - { - group: 'redis.log', - time: 1589078700000, - value: 5.880794701986755, - }, - { - group: 'redis.slowlog', - time: 1589078700000, - value: 5.880794701986755, - }, - { - group: 'unknown', - time: 1589214600000, - value: 7137.556291390729, - }, - { - group: 'kibana.log', - time: 1589214600000, - value: 178.26490066225165, - }, - { - group: 'nginx.access', - time: 1589214600000, - value: 798.9867549668874, - }, - { - group: 'nginx.error', - time: 1589214600000, - value: 123.67549668874172, - }, - { - group: 'postgresql.log', - time: 1589214600000, - value: 0.3973509933774834, - }, - { - group: 'redis.log', - time: 1589214600000, - value: 0.4304635761589404, - }, - { - group: 'redis.slowlog', - time: 1589214600000, - value: 0.4304635761589404, - }, - { - group: 'unknown', - time: 1589350500000, - value: 7510.6688741721855, - }, - { - group: 'kibana.log', - time: 1589350500000, - value: 152.63576158940398, - }, - { - group: 'nginx.access', - time: 1589350500000, - value: 823.3973509933775, - }, - { - group: 'nginx.error', - time: 1589350500000, - value: 126.11920529801324, - }, - { - group: 'postgresql.log', - time: 1589350500000, - value: 0.33112582781456956, - }, - { - group: 'redis.log', - time: 1589350500000, - value: 0.4370860927152318, - }, - { - group: 'redis.slowlog', - time: 1589350500000, - value: 0.4370860927152318, - }, - { - group: 'unknown', - time: 1589486400000, - value: 9013.788079470198, - }, - { - group: 'kibana.log', - time: 1589486400000, - value: 3.2781456953642385, - }, - { - group: 'nginx.access', - time: 1589486400000, - value: 883.3112582781457, - }, - { - group: 'nginx.error', - time: 1589486400000, - value: 144.1523178807947, - }, - { - group: 'postgresql.log', - time: 1589486400000, - value: 0.2980132450331126, - }, - { - group: 'redis.log', - time: 1589486400000, - value: 0.41721854304635764, - }, - { - group: 'redis.slowlog', - time: 1589486400000, - value: 0.41721854304635764, - }, - { - group: 'unknown', - time: 1589622300000, - value: 9022.754966887418, - }, - { - group: 'kibana.log', - time: 1589622300000, - value: 0.08609271523178808, - }, - { - group: 'nginx.access', - time: 1589622300000, - value: 840.6423841059602, - }, - { - group: 'nginx.error', - time: 1589622300000, - value: 144.74834437086093, - }, - { - group: 'postgresql.log', - time: 1589622300000, - value: 0.3973509933774834, - }, - { - group: 'redis.log', - time: 1589622300000, - value: 0.423841059602649, - }, - { - group: 'redis.slowlog', - time: 1589622300000, - value: 0.423841059602649, - }, - { - group: 'unknown', - time: 1589758200000, - value: 9042.53642384106, - }, - { - group: 'kibana.log', - time: 1589758200000, - value: 3.7218543046357615, - }, - { - group: 'nginx.access', - time: 1589758200000, - value: 863.8079470198676, - }, - { - group: 'nginx.error', - time: 1589758200000, - value: 144.40397350993376, - }, - { - group: 'postgresql.log', - time: 1589758200000, - value: 0.4768211920529801, - }, - { - group: 'redis.log', - time: 1589758200000, - value: 0.271523178807947, - }, - { - group: 'redis.slowlog', - time: 1589758200000, - value: 0.271523178807947, - }, - { - group: 'unknown', - time: 1589894100000, - value: 8690.76821192053, - }, - { - group: 'haproxy.log', - time: 1589894100000, - value: 0, - }, - { - group: 'kibana.log', - time: 1589894100000, - value: 0.8741721854304636, - }, - { - group: 'nginx.access', - time: 1589894100000, - value: 86.54966887417218, - }, - { - group: 'nginx.error', - time: 1589894100000, - value: 12.675496688741722, - }, - { - group: 'postgresql.log', - time: 1589894100000, - value: 0.09271523178807947, - }, - { - group: 'redis.log', - time: 1589894100000, - value: 0.3708609271523179, - }, - { - group: 'redis.slowlog', - time: 1589894100000, - value: 0.3973509933774834, - }, - { - group: 'unknown', - time: 1590030000000, - value: 8829.204545454546, - }, - { - group: 'haproxy.log', - time: 1590030000000, - value: 0, - }, - { - group: 'kibana.log', - time: 1590030000000, - value: 0, - }, - { - group: 'nginx.access', - time: 1590030000000, - value: 0, - }, - { - group: 'nginx.error', - time: 1590030000000, - value: 0, - }, - { - group: 'postgresql.log', - time: 1590030000000, - value: 0, - }, - { - group: 'redis.log', - time: 1590030000000, - value: 0, - }, - { - group: 'redis.slowlog', - time: 1590030000000, - value: 0, - }, -]; diff --git a/x-pack/plugins/observability/public/pages/overview/logs.mock.ts b/x-pack/plugins/observability/public/pages/overview/logs.mock.ts new file mode 100644 index 0000000000000..8e398e17f2309 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/logs.mock.ts @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FetchDataResponse } from '../../typings/data_handler'; + +export const logsData: FetchDataResponse = { + title: i18n.translate('xpack.logs.observabilityDashboard.title', { + defaultMessage: 'Logs', + }), + appLink: '/app/logs', + stats: [ + { label: 'unknown', value: 73777.20801439178 }, + { label: 'kibana.log', value: 1018.4591611479029 }, + { label: 'nginx.access', value: 5528.920109324083 }, + ], + series: [ + { + label: 'unknown', + coordinates: [ + { + x: 1588942800000, + y: 50, + }, + { + x: 1589078700000, + y: 100, + }, + { + x: 1589214600000, + y: 5000, + }, + { + x: 1589350500000, + y: 6000, + }, + { + x: 1589486400000, + y: 4000, + }, + { + x: 1589622300000, + y: 5000, + }, + { + x: 1589758200000, + y: 10000, + }, + { + x: 1589894100000, + y: 1500, + }, + { + x: 1590030000000, + y: 3000, + }, + ], + }, + { + label: 'kibana.log', + coordinates: [ + { + x: 1588942800000, + y: 50, + }, + { + x: 1589078700000, + y: 100, + }, + { + x: 1589214600000, + y: 5000, + }, + { + x: 1589350500000, + y: 6000, + }, + { + x: 1589486400000, + y: 4000, + }, + { + x: 1589622300000, + y: 5000, + }, + { + x: 1589758200000, + y: 10000, + }, + { + x: 1589894100000, + y: 1500, + }, + { + x: 1590030000000, + y: 3000, + }, + ], + }, + { + label: 'nginx.access', + coordinates: [ + { + x: 1588942800000, + y: 594.3174603174604, + }, + { + x: 1589078700000, + y: 637.9072847682119, + }, + { + x: 1589214600000, + y: 798.9867549668874, + }, + { + x: 1589350500000, + y: 823.3973509933775, + }, + { + x: 1589486400000, + y: 883.3112582781457, + }, + { + x: 1589622300000, + y: 840.6423841059602, + }, + { + x: 1589758200000, + y: 863.8079470198676, + }, + { + x: 1589894100000, + y: 86.54966887417218, + }, + { + x: 1590030000000, + y: 0, + }, + ], + }, + { + label: 'nginx.error', + coordinates: [ + { + x: 1588942800000, + y: 134.79365079365078, + }, + { + x: 1589078700000, + y: 135.9205298013245, + }, + { + x: 1589214600000, + y: 123.67549668874172, + }, + { + x: 1589350500000, + y: 126.11920529801324, + }, + { + x: 1589486400000, + y: 144.1523178807947, + }, + { + x: 1589622300000, + y: 144.74834437086093, + }, + { + x: 1589758200000, + y: 144.40397350993376, + }, + { + x: 1589894100000, + y: 12.675496688741722, + }, + { + x: 1590030000000, + y: 0, + }, + ], + }, + { + label: 'postgresql.log', + coordinates: [ + { + x: 1588942800000, + y: 0.1935483870967742, + }, + { + x: 1589078700000, + y: 0.4370860927152318, + }, + { + x: 1589214600000, + y: 0.3973509933774834, + }, + { + x: 1589350500000, + y: 0.33112582781456956, + }, + { + x: 1589486400000, + y: 0.2980132450331126, + }, + { + x: 1589622300000, + y: 0.3973509933774834, + }, + { + x: 1589758200000, + y: 0.4768211920529801, + }, + { + x: 1589894100000, + y: 0.09271523178807947, + }, + { + x: 1590030000000, + y: 0, + }, + ], + }, + { + label: 'redis.log', + coordinates: [ + { + x: 1588942800000, + y: 11.714285714285714, + }, + { + x: 1589078700000, + y: 5.880794701986755, + }, + { + x: 1589214600000, + y: 0.4304635761589404, + }, + { + x: 1589350500000, + y: 0.4370860927152318, + }, + { + x: 1589486400000, + y: 0.41721854304635764, + }, + { + x: 1589622300000, + y: 0.423841059602649, + }, + { + x: 1589758200000, + y: 0.271523178807947, + }, + { + x: 1589894100000, + y: 0.3708609271523179, + }, + { + x: 1590030000000, + y: 0, + }, + ], + }, + { + label: 'redis.slowlog', + coordinates: [ + { + x: 1588942800000, + y: 11.714285714285714, + }, + { + x: 1589078700000, + y: 5.880794701986755, + }, + { + x: 1589214600000, + y: 0.4304635761589404, + }, + { + x: 1589350500000, + y: 0.4370860927152318, + }, + { + x: 1589486400000, + y: 0.41721854304635764, + }, + { + x: 1589622300000, + y: 0.423841059602649, + }, + { + x: 1589758200000, + y: 0.271523178807947, + }, + { + x: 1589894100000, + y: 0.3973509933774834, + }, + { + x: 1590030000000, + y: 0, + }, + ], + }, + { + label: 'haproxy.log', + coordinates: [ + { + x: 1589894100000, + y: 0, + }, + { + x: 1590030000000, + y: 0, + }, + ], + }, + ], +}; diff --git a/x-pack/plugins/observability/public/pages/overview/uptime.data.ts b/x-pack/plugins/observability/public/pages/overview/uptime.data.ts deleted file mode 100644 index e8127a66c4165..0000000000000 --- a/x-pack/plugins/observability/public/pages/overview/uptime.data.ts +++ /dev/null @@ -1,168 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const barData = [ - { - time: 1591439940000, - value: 2, - group: 'Down', - }, - { - time: 1591439940000, - value: 12, - group: 'Up', - }, - { - time: 1591440000000, - value: 6, - group: 'Down', - }, - { - time: 1591440000000, - value: 30, - group: 'Up', - }, - { - time: 1591440060000, - value: 6, - group: 'Down', - }, - { - time: 1591440060000, - value: 30, - group: 'Up', - }, - { - time: 1591440120000, - value: 6, - group: 'Down', - }, - { - time: 1591440120000, - value: 30, - group: 'Up', - }, - { - time: 1591440180000, - value: 15, - group: 'Down', - }, - { - time: 1591440180000, - value: 75, - group: 'Up', - }, - { - time: 1591440240000, - value: 6, - group: 'Down', - }, - { - time: 1591440240000, - value: 30, - group: 'Up', - }, - { - time: 1591440300000, - value: 6, - group: 'Down', - }, - { - time: 1591440300000, - value: 30, - group: 'Up', - }, - { - time: 1591440360000, - value: 6, - group: 'Down', - }, - { - time: 1591440360000, - value: 30, - group: 'Up', - }, - { - time: 1591440420000, - value: 6, - group: 'Down', - }, - { - time: 1591440420000, - value: 30, - group: 'Up', - }, - { - time: 1591440480000, - value: 15, - group: 'Down', - }, - { - time: 1591440480000, - value: 75, - group: 'Up', - }, - { - time: 1591440540000, - value: 6, - group: 'Down', - }, - { - time: 1591440540000, - value: 30, - group: 'Up', - }, - { - time: 1591440600000, - value: 6, - group: 'Down', - }, - { - time: 1591440600000, - value: 30, - group: 'Up', - }, - { - time: 1591440660000, - value: 6, - group: 'Down', - }, - { - time: 1591440660000, - value: 30, - group: 'Up', - }, - { - time: 1591440720000, - value: 6, - group: 'Down', - }, - { - time: 1591440720000, - value: 30, - group: 'Up', - }, - { - time: 1591440780000, - value: 15, - group: 'Down', - }, - { - time: 1591440780000, - value: 75, - group: 'Up', - }, - { - time: 1591440840000, - value: 2, - group: 'Down', - }, - { - time: 1591440840000, - value: 15, - group: 'Up', - }, -]; diff --git a/x-pack/plugins/observability/public/pages/overview/uptime.mock.ts b/x-pack/plugins/observability/public/pages/overview/uptime.mock.ts new file mode 100644 index 0000000000000..1408e15adf228 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/uptime.mock.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FetchDataResponse } from '../../typings/data_handler'; + +export const uptimeData: FetchDataResponse = { + title: 'Uptiome', + appLink: '/app/uptime', + stats: [ + { + label: 'Down', + value: 115, + }, + { + label: 'Up', + value: 582, + }, + ], + series: [ + { + label: 'Down', + color: 'euiColorVis2', + coordinates: [ + { + x: 1591439940000, + y: 2, + }, + { + x: 1591440000000, + y: 6, + }, + { + x: 1591440060000, + y: 6, + }, + { + x: 1591440120000, + y: 6, + }, + { + x: 1591440180000, + y: 15, + }, + { + x: 1591440240000, + y: 6, + }, + { + x: 1591440300000, + y: 6, + }, + { + x: 1591440360000, + y: 6, + }, + { + x: 1591440420000, + y: 6, + }, + { + x: 1591440480000, + y: 15, + }, + { + x: 1591440540000, + y: 6, + }, + { + x: 1591440600000, + y: 6, + }, + { + x: 1591440660000, + y: 6, + }, + { + x: 1591440720000, + y: 6, + }, + { + x: 1591440780000, + y: 15, + }, + { + x: 1591440840000, + y: 2, + }, + ], + }, + { + label: 'Up', + color: 'euiColorLightShade', + coordinates: [ + { + x: 1591439940000, + y: 12, + }, + { + x: 1591440000000, + y: 30, + }, + { + x: 1591440060000, + y: 30, + }, + { + x: 1591440120000, + y: 30, + }, + { + x: 1591440180000, + y: 75, + }, + { + x: 1591440240000, + y: 30, + }, + { + x: 1591440300000, + y: 30, + }, + { + x: 1591440360000, + y: 30, + }, + { + x: 1591440420000, + y: 30, + }, + { + x: 1591440480000, + y: 75, + }, + { + x: 1591440540000, + y: 30, + }, + { + x: 1591440600000, + y: 30, + }, + { + x: 1591440660000, + y: 30, + }, + { + x: 1591440720000, + y: 30, + }, + { + x: 1591440780000, + y: 75, + }, + { + x: 1591440840000, + y: 15, + }, + ], + }, + ], +}; diff --git a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts b/x-pack/plugins/observability/public/typings/data_handler/index.d.ts index 5374beb41bd70..93461f8ee395c 100644 --- a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts +++ b/x-pack/plugins/observability/public/typings/data_handler/index.d.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -interface Stat { +export interface Stat { label: string; - value: string; + value: number; color?: string; } From 3f4eab380987bf8d586993fb1f96bd9b50806110 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 16 Jun 2020 17:48:51 +0200 Subject: [PATCH 33/97] changing mock data location --- .../public/components/chart/apm/index.tsx | 12 ++++++++++-- .../public/components/chart/metrics/index.tsx | 13 +++++++++++-- .../observability/public/pages/home/index.tsx | 2 +- .../apm/mock.data.ts => pages/overview/apm.mock.ts} | 0 .../observability/public/pages/overview/index.tsx | 6 ++++-- .../mock.data.ts => pages/overview/metrics.mock.ts} | 2 +- 6 files changed, 27 insertions(+), 8 deletions(-) rename x-pack/plugins/observability/public/{components/chart/apm/mock.data.ts => pages/overview/apm.mock.ts} (100%) rename x-pack/plugins/observability/public/{components/chart/metrics/mock.data.ts => pages/overview/metrics.mock.ts} (97%) diff --git a/x-pack/plugins/observability/public/components/chart/apm/index.tsx b/x-pack/plugins/observability/public/components/chart/apm/index.tsx index de5471ce0e9a7..0a4ee9f11da1f 100644 --- a/x-pack/plugins/observability/public/components/chart/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/apm/index.tsx @@ -20,11 +20,19 @@ import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { apmData as data } from './mock.data'; +import { FetchDataResponse } from '../../../typings/data_handler'; -export const APMChart = () => { +interface Props { + data?: FetchDataResponse; +} + +export const APMChart = ({ data }: Props) => { const theme = useContext(ThemeContext); + if (!data) { + return null; + } + const transactionSeries = data.series.find((d) => d.key === 'transactions'); const errorSeries = data.series.find((d) => d.key === 'errors'); diff --git a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx index dfd0891a04cca..032724109d78e 100644 --- a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx @@ -9,10 +9,19 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { data } from './mock.data'; +import { FetchDataResponse } from '../../../typings/data_handler'; -export const MetricsChart = () => { +interface Props { + data?: FetchDataResponse; +} + +export const MetricsChart = ({ data }: Props) => { const theme = useContext(ThemeContext); + + if (!data) { + return null; + } + return ( diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 78c8f775d6788..3524d869e43a1 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -80,7 +80,7 @@ export const Home = () => { {apps.map((app) => ( - + { const theme = useContext(ThemeContext); @@ -45,10 +47,10 @@ export const Overview = () => { - + - + diff --git a/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts b/x-pack/plugins/observability/public/pages/overview/metrics.mock.ts similarity index 97% rename from x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts rename to x-pack/plugins/observability/public/pages/overview/metrics.mock.ts index e06d76769da1f..f235f685c1ef4 100644 --- a/x-pack/plugins/observability/public/components/chart/metrics/mock.data.ts +++ b/x-pack/plugins/observability/public/pages/overview/metrics.mock.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { FetchDataResponse } from '../../../typings/data_handler'; -export const data: FetchDataResponse = { +export const metricsData: FetchDataResponse = { title: i18n.translate('metrics.observabilityDashboard.title', { defaultMessage: 'Metrics', }), From 51c6230444115ec8cf7f885049763894459678e7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 17 Jun 2020 09:07:58 +0200 Subject: [PATCH 34/97] adding mock registry --- .../public/hooks/use_fetcher.tsx | 71 ++++++++++++++++++ .../{pages/overview => mock}/apm.mock.ts | 8 ++- .../{pages/overview => mock}/logs.mock.ts | 8 ++- .../{pages/overview => mock}/metrics.mock.ts | 8 ++- .../{pages/overview => mock}/uptime.mock.ts | 8 ++- .../public/pages/home/section.ts | 18 ++--- .../pages/overview/continue_journey.tsx | 38 ---------- .../public/pages/overview/index.tsx | 72 ++++++++++++++++--- x-pack/plugins/observability/public/plugin.ts | 25 +++++++ 9 files changed, 187 insertions(+), 69 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/use_fetcher.tsx rename x-pack/plugins/observability/public/{pages/overview => mock}/apm.mock.ts (98%) rename x-pack/plugins/observability/public/{pages/overview => mock}/logs.mock.ts (96%) rename x-pack/plugins/observability/public/{pages/overview => mock}/metrics.mock.ts (91%) rename x-pack/plugins/observability/public/{pages/overview => mock}/uptime.mock.ts (93%) delete mode 100644 x-pack/plugins/observability/public/pages/overview/continue_journey.tsx diff --git a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx new file mode 100644 index 0000000000000..3b7a08fa66b02 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useMemo } from 'react'; + +export enum FETCH_STATUS { + LOADING = 'loading', + SUCCESS = 'success', + FAILURE = 'failure', + PENDING = 'pending', +} + +export interface FetcherResult { + data?: Data; + status: FETCH_STATUS; + error?: Error; +} + +// fetcher functions can return undefined OR a promise. Previously we had a more simple type +// but it led to issues when using object destructuring with default values +type InferResponseType = Exclude extends Promise + ? TResponseType + : unknown; + +export function useFetcher( + fn: () => TReturn, + fnDeps: any[] +): FetcherResult> & { refetch: () => void } { + const [result, setResult] = useState>>({ + data: undefined, + status: FETCH_STATUS.PENDING, + }); + const [counter, setCounter] = useState(0); + useEffect(() => { + async function doFetch() { + const promise = fn(); + if (!promise) { + return; + } + + setResult((prevResult) => ({ + data: undefined, + status: FETCH_STATUS.LOADING, + error: undefined, + })); + + const data = await promise; + setResult({ + data, + status: FETCH_STATUS.SUCCESS, + error: undefined, + } as FetcherResult>); + } + + doFetch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [counter, ...fnDeps]); + + return useMemo(() => { + return { + ...result, + refetch: () => { + // this will invalidate the deps to `useEffect` and will result in a new request + setCounter((count) => count + 1); + }, + }; + }, [result]); +} diff --git a/x-pack/plugins/observability/public/pages/overview/apm.mock.ts b/x-pack/plugins/observability/public/mock/apm.mock.ts similarity index 98% rename from x-pack/plugins/observability/public/pages/overview/apm.mock.ts rename to x-pack/plugins/observability/public/mock/apm.mock.ts index 8541b3f24695c..1f441c6804984 100644 --- a/x-pack/plugins/observability/public/pages/overview/apm.mock.ts +++ b/x-pack/plugins/observability/public/mock/apm.mock.ts @@ -5,9 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -import { FetchDataResponse } from '../../../typings/data_handler'; +import { FetchDataResponse, FetchData } from '../typings/data_handler'; -export const apmData: FetchDataResponse = { +export const fetchApmData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: FetchDataResponse = { title: i18n.translate('apm.observabilityDashboard.title', { defaultMessage: 'APM', }), diff --git a/x-pack/plugins/observability/public/pages/overview/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts similarity index 96% rename from x-pack/plugins/observability/public/pages/overview/logs.mock.ts rename to x-pack/plugins/observability/public/mock/logs.mock.ts index 8e398e17f2309..7e219d716eef9 100644 --- a/x-pack/plugins/observability/public/pages/overview/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -5,9 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -import { FetchDataResponse } from '../../typings/data_handler'; +import { FetchDataResponse, FetchData } from '../typings/data_handler'; -export const logsData: FetchDataResponse = { +export const fetchLogsData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: FetchDataResponse = { title: i18n.translate('xpack.logs.observabilityDashboard.title', { defaultMessage: 'Logs', }), diff --git a/x-pack/plugins/observability/public/pages/overview/metrics.mock.ts b/x-pack/plugins/observability/public/mock/metrics.mock.ts similarity index 91% rename from x-pack/plugins/observability/public/pages/overview/metrics.mock.ts rename to x-pack/plugins/observability/public/mock/metrics.mock.ts index f235f685c1ef4..14eb09fc985b5 100644 --- a/x-pack/plugins/observability/public/pages/overview/metrics.mock.ts +++ b/x-pack/plugins/observability/public/mock/metrics.mock.ts @@ -5,9 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -import { FetchDataResponse } from '../../../typings/data_handler'; +import { FetchDataResponse, FetchData } from '../typings/data_handler'; -export const metricsData: FetchDataResponse = { +export const fetchMetricsData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: FetchDataResponse = { title: i18n.translate('metrics.observabilityDashboard.title', { defaultMessage: 'Metrics', }), diff --git a/x-pack/plugins/observability/public/pages/overview/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts similarity index 93% rename from x-pack/plugins/observability/public/pages/overview/uptime.mock.ts rename to x-pack/plugins/observability/public/mock/uptime.mock.ts index 1408e15adf228..9783143bd7864 100644 --- a/x-pack/plugins/observability/public/pages/overview/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -3,9 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { FetchDataResponse } from '../../typings/data_handler'; +import { FetchDataResponse, FetchData } from '../typings/data_handler'; -export const uptimeData: FetchDataResponse = { +export const fetchUptimeData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: FetchDataResponse = { title: 'Uptiome', appLink: '/app/uptime', stats: [ diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index d8765d45c0aa1..84a178b91beda 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; +import { ObservabilityApp } from '../../../typings/common'; export interface ISection { - id: string; + id: ObservabilityApp; title: string; icon: string; description: string; @@ -17,7 +18,7 @@ export interface ISection { export const appsSection: ISection[] = [ { - id: 'logs', + id: 'infra_logs', title: i18n.translate('xpack.observability.section.apps.logs.title', { defaultMessage: 'Logs', }), @@ -47,7 +48,7 @@ export const appsSection: ISection[] = [ href: 'https://www.elastic.co', }, { - id: 'metrics', + id: 'infra_metrics', title: i18n.translate('xpack.observability.section.apps.metrics.title', { defaultMessage: 'Metrics', }), @@ -76,15 +77,4 @@ export const appsSection: ISection[] = [ }), href: 'https://www.elastic.co', }, - // { - // id: 'alert', - // title: i18n.translate('xpack.observability.section.apps.alert.title', { - // defaultMessage: 'Alert', - // }), - // icon: 'watchesApp', - // description: i18n.translate('xpack.observability.section.apps.alert.description', { - // defaultMessage: - // '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', - // }), - // }, ]; diff --git a/x-pack/plugins/observability/public/pages/overview/continue_journey.tsx b/x-pack/plugins/observability/public/pages/overview/continue_journey.tsx deleted file mode 100644 index 9c55d7576626c..0000000000000 --- a/x-pack/plugins/observability/public/pages/overview/continue_journey.tsx +++ /dev/null @@ -1,38 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { EuiFlexGrid, EuiFlexItem, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EmptySection } from '../../components/empty_section'; -import { ISection } from '../home/section'; - -interface Props { - apps: ISection[]; -} - -export const ContinueJourney = ({ apps }: Props) => { - return ( - <> - -
- {i18n.translate('xpack.observability.continueJorney.title', { - defaultMessage: 'Continue your Observability journey...', - })} -
-
- - - {apps.map((app) => { - return ( - - - - ); - })} - - - ); -}; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 410846959d5dc..1969058d03ae9 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -3,26 +3,72 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiDatePicker, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui'; +import { + EuiDatePicker, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiSwitch, +} from '@elastic/eui'; import moment from 'moment'; import React, { useContext, useState } from 'react'; import { ThemeContext } from 'styled-components'; +import { isEmpty } from 'lodash'; import { APMChart } from '../../components/chart/apm'; import { ChartContainer } from '../../components/chart/container'; -import { WithHeaderLayout } from '../../components/layout/with_header'; -import { appsSection } from '../home/section'; -import { ContinueJourney } from './continue_journey'; import { MetricsChart } from '../../components/chart/metrics'; import { StackedBarChart } from '../../components/chart/stacked_bar'; -import { logsData } from './logs.mock'; -import { uptimeData } from './uptime.mock'; -import { metricsData } from './metrics.mock'; -import { apmData } from './apm.mock'; +import { EmptySection } from '../../components/empty_section'; +import { WithHeaderLayout } from '../../components/layout/with_header'; +import { appsSection } from '../home/section'; +import { getDataHandler } from '../../data_handler'; +import { useFetcher } from '../../hooks/use_fetcher'; export const Overview = () => { const theme = useContext(ThemeContext); const [withAlert, setWithAlert] = useState(false); + const { data: apmData } = useFetcher(() => { + const dataHandler = getDataHandler('apm'); + if (dataHandler) { + return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); + } + }, []); + const { data: logsData } = useFetcher(() => { + const dataHandler = getDataHandler('infra_logs'); + if (dataHandler) { + return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); + } + }, []); + const { data: metricsData } = useFetcher(() => { + const dataHandler = getDataHandler('infra_metrics'); + if (dataHandler) { + return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); + } + }, []); + const { data: uptimeData } = useFetcher(() => { + const dataHandler = getDataHandler('uptime'); + if (dataHandler) { + return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); + } + }, []); + + const emptySections = appsSection.filter((app) => { + switch (app.id) { + case 'apm': + return isEmpty(apmData); + case 'infra_logs': + return isEmpty(logsData); + case 'infra_metrics': + return isEmpty(metricsData); + case 'uptime': + return isEmpty(uptimeData); + default: + return true; + } + }); + return ( { - + + {emptySections.map((app) => { + return ( + + + + ); + })} + ); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 16adf88d152c5..6448a669e7e96 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -11,6 +11,10 @@ import { PluginInitializerContext, } from '../../../../src/core/public'; import { RegisterDataHandler, registerDataHandler } from './data_handler'; +import { fetchApmData } from './mock/apm.mock'; +import { fetchLogsData } from './mock/logs.mock'; +import { fetchMetricsData } from './mock/metrics.mock'; +import { fetchUptimeData } from './mock/uptime.mock'; export interface ObservabilityPluginSetup { dashboard: { register: RegisterDataHandler }; @@ -39,6 +43,27 @@ export class Plugin implements PluginClass Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: () => Promise.resolve(true), + }); + return { dashboard: { register: registerDataHandler }, }; From fcd8e4c8c67a9f2f09e2c3a6e53659fe1ea4875a Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 17 Jun 2020 15:24:45 +0200 Subject: [PATCH 35/97] adding date picker --- src/plugins/data/common/constants.ts | 3 +- .../components/shared/data_picker/index.tsx | 107 ++++++++++++++++++ .../public/hooks/use_kibana_ui_settings.tsx | 18 +++ .../public/hooks/use_query_params.tsx | 16 +++ .../public/pages/overview/index.tsx | 62 ++++------ .../observability/public/utils/date.ts | 15 +++ .../plugins/observability/public/utils/url.ts | 19 ++++ 7 files changed, 202 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/data_picker/index.tsx create mode 100644 x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx create mode 100644 x-pack/plugins/observability/public/hooks/use_query_params.tsx create mode 100644 x-pack/plugins/observability/public/utils/date.ts create mode 100644 x-pack/plugins/observability/public/utils/url.ts diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 8ec72dc1f9a74..22db1552e4303 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -44,7 +44,8 @@ export const UI_SETTINGS = { FORMAT_NUMBER_DEFAULT_LOCALE: 'format:number:defaultLocale', TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: 'timepicker:refreshIntervalDefaults', TIMEPICKER_QUICK_RANGES: 'timepicker:quickRanges', + TIMEPICKER_TIME_DEFAULTS: 'timepicker:timeDefaults', INDEXPATTERN_PLACEHOLDER: 'indexPattern:placeholder', FILTERS_PINNED_BY_DEFAULT: 'filters:pinnedByDefault', FILTERS_EDITOR_SUGGEST_VALUES: 'filterEditor:suggestValues', -}; +} as const; diff --git a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx new file mode 100644 index 0000000000000..f206d583270bc --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiSuperDatePicker } from '@elastic/eui'; +import { stringify } from 'query-string'; +import React from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings'; +import { useQueryParams } from '../../../hooks/use_query_params'; +import { fromQuery, toQuery } from '../../../utils/url'; + +export interface TimePickerTime { + from: string; + to: string; +} + +interface TimePickerQuickRange extends TimePickerTime { + display: string; +} + +interface TimePickerRefreshInterval { + pause: boolean; + value: number; +} + +interface QueryParams { + rangeFrom: string; + rangeTo: string; + refreshPaused: boolean; + refreshInterval: number; +} + +interface Props { + rangeFrom: string; + rangeTo: string; +} + +export const DatePicker = ({ rangeFrom, rangeTo }: Props) => { + const location = useLocation(); + const history = useHistory(); + + // TODO: check if it can be done in another way + const timePickerQuickRanges = useKibanaUISettings( + UI_SETTINGS.TIMEPICKER_QUICK_RANGES + ); + const timePickerRefreshInterval = useKibanaUISettings( + UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS + ); + + const commonlyUsedRanges = timePickerQuickRanges.map(({ from, to, display }) => ({ + start: from, + end: to, + label: display, + })); + + function updateUrl(nextQuery: { + rangeFrom?: string; + rangeTo?: string; + refreshPaused?: boolean; + refreshInterval?: number; + }) { + history.push({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + ...nextQuery, + }), + }); + } + + function onRefreshChange({ + isPaused, + refreshInterval, + }: { + isPaused: boolean; + refreshInterval: number; + }) { + updateUrl({ refreshPaused: isPaused, refreshInterval }); + } + + function onTimeChange({ start, end }: { start: string; end: string }) { + updateUrl({ rangeFrom: start, rangeTo: end }); + } + + // TODO: maybe use Generics to specify what this component expects + const { + refreshPaused = true, + refreshInterval = timePickerRefreshInterval.value, + } = useQueryParams(); + + return ( + + ); +}; diff --git a/x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx b/x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx new file mode 100644 index 0000000000000..884d74db391ee --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { usePluginContext } from './use_plugin_context'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/public'; + +export { UI_SETTINGS }; + +type SettingKeys = keyof typeof UI_SETTINGS; +type SettingValues = typeof UI_SETTINGS[SettingKeys]; + +export function useKibanaUISettings(key: SettingValues): T { + const { core } = usePluginContext(); + return core.uiSettings.get(key); +} diff --git a/x-pack/plugins/observability/public/hooks/use_query_params.tsx b/x-pack/plugins/observability/public/hooks/use_query_params.tsx new file mode 100644 index 0000000000000..eea04ec8831e0 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_query_params.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { useLocation } from 'react-router-dom'; + +export function useQueryParams(): T { + const urlSearchParms = new URLSearchParams(useLocation().search); + const params: Record = {}; + urlSearchParms.forEach((value, key) => { + params[key] = value; + }); + // TODO: check it later + return (params as unknown) as T; +} diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 1969058d03ae9..4db72a2cb047e 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -24,35 +24,32 @@ import { WithHeaderLayout } from '../../components/layout/with_header'; import { appsSection } from '../home/section'; import { getDataHandler } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; +import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; +import { useQueryParams } from '../../hooks/use_query_params'; +import { getParsedDate } from '../../utils/date'; +import { useKibanaUISettings, UI_SETTINGS } from '../../hooks/use_kibana_ui_settings'; export const Overview = () => { const theme = useContext(ThemeContext); - const [withAlert, setWithAlert] = useState(false); + const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); - const { data: apmData } = useFetcher(() => { - const dataHandler = getDataHandler('apm'); - if (dataHandler) { - return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); - } - }, []); - const { data: logsData } = useFetcher(() => { - const dataHandler = getDataHandler('infra_logs'); - if (dataHandler) { - return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); - } - }, []); - const { data: metricsData } = useFetcher(() => { - const dataHandler = getDataHandler('infra_metrics'); - if (dataHandler) { - return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); - } - }, []); - const { data: uptimeData } = useFetcher(() => { - const dataHandler = getDataHandler('uptime'); - if (dataHandler) { - return dataHandler.fetchData({ startTime: '1', endTime: '2', bucketSize: '3' }); + const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to } = useQueryParams(); + + const { data = [] } = useFetcher(() => { + const startTime = getParsedDate(rangeFrom); + const endTime = getParsedDate(rangeTo); + if (startTime && endTime) { + const params = { startTime, endTime, bucketSize: '3' }; + const apmHandler = getDataHandler('apm')?.fetchData(params); + const logsHandler = getDataHandler('infra_logs')?.fetchData(params); + const metricsHandler = getDataHandler('infra_metrics')?.fetchData(params); + const uptimeHandler = getDataHandler('uptime')?.fetchData(params); + + return Promise.all([apmHandler, logsHandler, metricsHandler, uptimeHandler]); } - }, []); + }, [rangeFrom, rangeTo]); + + const [apmData, logsData, metricsData, uptimeData] = data; const emptySections = appsSection.filter((app) => { switch (app.id) { @@ -76,16 +73,9 @@ export const Overview = () => { > - {}} /> + - - setWithAlert((currState) => !currState)} - /> - @@ -103,11 +93,9 @@ export const Overview = () => {
- {withAlert && ( - - chart goes here - - )} + + chart goes here +
diff --git a/x-pack/plugins/observability/public/utils/date.ts b/x-pack/plugins/observability/public/utils/date.ts new file mode 100644 index 0000000000000..fc0bbdae20cb9 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/date.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import datemath from '@elastic/datemath'; + +export function getParsedDate(range?: string, opts = {}) { + if (range) { + const parsed = datemath.parse(range, opts); + if (parsed) { + return parsed.toISOString(); + } + } +} diff --git a/x-pack/plugins/observability/public/utils/url.ts b/x-pack/plugins/observability/public/utils/url.ts new file mode 100644 index 0000000000000..962ab8233a8f5 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/url.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { parse, stringify } from 'query-string'; +import { url } from '../../../../../src/plugins/kibana_utils/public'; + +export function toQuery(search?: string) { + return search ? parse(search.slice(1), { sort: false }) : {}; +} + +export function fromQuery(query: Record) { + const encodedQuery = url.encodeQuery(query, (value) => + encodeURIComponent(value).replace(/%3A/g, ':') + ); + + return stringify(encodedQuery, { sort: false, encode: false }); +} From d34e1b3d9bc87ba2893b554f90b671316f5a7cc0 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 18 Jun 2020 08:57:04 +0200 Subject: [PATCH 36/97] adding route validation --- .../public/application/index.tsx | 28 ++++++-- .../public/hooks/use_url_params.tsx | 48 ++++++++++++++ .../observability/public/routes/index.tsx | 66 +++++++++++++++++++ .../observability/public/routes/json_rt.ts | 22 +++++++ 4 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/use_url_params.tsx create mode 100644 x-pack/plugins/observability/public/routes/index.tsx create mode 100644 x-pack/plugins/observability/public/routes/json_rt.ts diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index f287aa97482b8..5646b142ac7cd 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -3,22 +3,40 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import * as t from 'io-ts'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Route, Router, Switch } from 'react-router-dom'; +import { Route, Router, Switch, useLocation } from 'react-router-dom'; import { createHashHistory } from 'history'; +import { isLeft } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; -import { Home } from '../pages/home'; -import { Overview } from '../pages/overview'; import { PluginContext } from '../context/plugin_context'; +import { routes } from '../routes'; +import { useUrlParams } from '../hooks/use_url_params'; const App = () => { return ( <> - - + {Object.keys(routes).map((path) => { + const route = routes[path]; + return ( + { + const { query, path: pathParams } = useUrlParams(route); + + return route.handler({ query, path: pathParams }); + }} + /> + ); + })} + {/* + */} ); diff --git a/x-pack/plugins/observability/public/hooks/use_url_params.tsx b/x-pack/plugins/observability/public/hooks/use_url_params.tsx new file mode 100644 index 0000000000000..d1882cdeb579f --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_url_params.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { useLocation, useParams } from 'react-router-dom'; +import { isLeft } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; +import { Route } from '../routes'; + +function getQueryParams(location: ReturnType) { + const urlSearchParms = new URLSearchParams(location.search); + const queryParams: Record = {}; + urlSearchParms.forEach((value, key) => { + queryParams[key] = value; + }); + return queryParams; +} + +export function useUrlParams(route: Route) { + const location = useLocation(); + const pathParams = useParams(); + const queryParams = getQueryParams(location); + + const { params } = route; + const rts = { + queryRt: params?.query ? t.exact(params.query) : t.strict({}), + pathRt: params?.path ? t.exact(params.path) : t.strict({}), + }; + + const queryResult = rts.queryRt.decode(queryParams); + const pathResult = rts.pathRt.decode(pathParams); + if (isLeft(queryResult)) { + // eslint-disable-next-line no-console + console.error(PathReporter.report(queryResult)[0]); + } + + if (isLeft(pathResult)) { + // eslint-disable-next-line no-console + console.error(PathReporter.report(pathResult)[0]); + } + + return { + query: isLeft(queryResult) ? {} : queryResult.right, + path: isLeft(pathResult) ? {} : pathResult.right, + }; +} diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx new file mode 100644 index 0000000000000..609ad7495e3cb --- /dev/null +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import * as t from 'io-ts'; +import { Home } from '../pages/home'; +import { Overview } from '../pages/overview'; +import { jsonRt } from './json_rt'; + +// "/services/{serviceName}/errors": { +// params: { +// path: t.type({ +// serviceName: t.string +// }), +// query: t.partial({ +// rangeFrom: t.string, +// rangeTo: t.string +// }) +// }, +// handler: async ({ query, path }) => { +// return ; +// } +// } + +export interface Route { + handler: React.ReactNode; + params?: { + path?: any; + query?: any; + }; +} + +export const routes: Record = { + '/': { + handler: () => { + return ; + }, + }, + '/overview': { + handler: ({ query, path }: { query: any; path: any }) => { + console.log('### caue:', { query, path }); + return ; + }, + params: { + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + }), + }, + }, + '/overview/:id': { + handler: ({ query, path }: { query: any; path: any }) => { + console.log('### caue: with ID query', { query, path }); + return ; + }, + params: { + path: t.type({ id: jsonRt.pipe(t.number) }), + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + }), + }, + }, +}; diff --git a/x-pack/plugins/observability/public/routes/json_rt.ts b/x-pack/plugins/observability/public/routes/json_rt.ts new file mode 100644 index 0000000000000..c8ce1eb669a6b --- /dev/null +++ b/x-pack/plugins/observability/public/routes/json_rt.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { either } from 'fp-ts/lib/Either'; + +export const jsonRt = new t.Type( + 'JSON', + t.any.is, + (input, context) => + either.chain(t.string.validate(input, context), (str) => { + try { + console.log('#############3', str); + return t.success(JSON.parse(str)); + } catch (e) { + return t.failure(input, context); + } + }), + (a) => JSON.stringify(a) +); From 68f4af36016f4cba69ce2c344767cbfe0cd86aa3 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 18 Jun 2020 13:46:49 +0200 Subject: [PATCH 37/97] adding io-ts --- .../public/pages/overview/index.tsx | 8 +++- .../observability/public/routes/index.tsx | 43 +++++++++---------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 4db72a2cb047e..e344847beb6d3 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -28,8 +28,14 @@ import { DatePicker, TimePickerTime } from '../../components/shared/data_picker' import { useQueryParams } from '../../hooks/use_query_params'; import { getParsedDate } from '../../utils/date'; import { useKibanaUISettings, UI_SETTINGS } from '../../hooks/use_kibana_ui_settings'; +import { RouteParams } from '../../routes'; -export const Overview = () => { +interface Props { + routeParams?: RouteParams<'/overview'>; +} + +export const Overview = ({ routeParams }: Props) => { + console.log('### caue: Overview -> routeParams', routeParams?.params?.query); const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 609ad7495e3cb..ccc69b5543e58 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -9,38 +9,38 @@ import { Home } from '../pages/home'; import { Overview } from '../pages/overview'; import { jsonRt } from './json_rt'; -// "/services/{serviceName}/errors": { -// params: { -// path: t.type({ -// serviceName: t.string -// }), -// query: t.partial({ -// rangeFrom: t.string, -// rangeTo: t.string -// }) -// }, -// handler: async ({ query, path }) => { -// return ; -// } -// } +export interface RouteParams { + params: DecodeParams; +} + +type DecodeParams = { + [key in keyof TParams]: TParams[key] extends t.Any ? t.TypeOf : never; +}; -export interface Route { - handler: React.ReactNode; - params?: { - path?: any; - query?: any; +type OverviewRouteParams = DecodeParams; +const overviewRouteParams: OverviewRouteParams = { query: {} }; +console.log('### caue:', overviewRouteParams); + +export interface Params { + query?: t.Any; + path?: t.Any; +} +interface Routes { + [pathName: string]: { + handler: React.ReactNode; + params: Params; }; } -export const routes: Record = { +export const routes = { '/': { handler: () => { return ; }, + params: {}, }, '/overview': { handler: ({ query, path }: { query: any; path: any }) => { - console.log('### caue:', { query, path }); return ; }, params: { @@ -52,7 +52,6 @@ export const routes: Record = { }, '/overview/:id': { handler: ({ query, path }: { query: any; path: any }) => { - console.log('### caue: with ID query', { query, path }); return ; }, params: { From e36eb192ece363057871652ca8b55a47c2a8914f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 18 Jun 2020 13:47:16 +0200 Subject: [PATCH 38/97] adding io-ts --- x-pack/plugins/observability/public/routes/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index ccc69b5543e58..d46d3bc9c8ad0 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -32,7 +32,7 @@ interface Routes { }; } -export const routes = { +export const routes: Routes = { '/': { handler: () => { return ; From 58dd5f1951e2e5b2836a708fdfd6d4865270e5ec Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 23 Jun 2020 09:17:51 +0200 Subject: [PATCH 39/97] adding io-ts support --- .../public/application/index.tsx | 17 +++++------ .../public/hooks/use_url_params.tsx | 8 +++--- .../public/pages/overview/index.tsx | 28 ++++++------------- .../observability/public/routes/index.tsx | 26 +++++------------ 4 files changed, 27 insertions(+), 52 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 5646b142ac7cd..d6dccc6c79270 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -3,24 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import * as t from 'io-ts'; +import { createHashHistory } from 'history'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Route, Router, Switch, useLocation } from 'react-router-dom'; -import { createHashHistory } from 'history'; -import { isLeft } from 'fp-ts/lib/Either'; -import { PathReporter } from 'io-ts/lib/PathReporter'; -import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; +import { Route, Router, Switch } from 'react-router-dom'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; +import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { PluginContext } from '../context/plugin_context'; -import { routes } from '../routes'; import { useUrlParams } from '../hooks/use_url_params'; +import { routes } from '../routes'; const App = () => { return ( <> - {Object.keys(routes).map((path) => { + {Object.keys(routes).map((key) => { + const path = key as keyof typeof routes; const route = routes[path]; return ( { path={path} exact={true} component={() => { - const { query, path: pathParams } = useUrlParams(route); - + const { query, path: pathParams } = useUrlParams(route); return route.handler({ query, path: pathParams }); }} /> diff --git a/x-pack/plugins/observability/public/hooks/use_url_params.tsx b/x-pack/plugins/observability/public/hooks/use_url_params.tsx index d1882cdeb579f..0f5adbc46f1c7 100644 --- a/x-pack/plugins/observability/public/hooks/use_url_params.tsx +++ b/x-pack/plugins/observability/public/hooks/use_url_params.tsx @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { useLocation, useParams } from 'react-router-dom'; import { isLeft } from 'fp-ts/lib/Either'; import { PathReporter } from 'io-ts/lib/PathReporter'; -import { Route } from '../routes'; +import { routes } from '../routes'; function getQueryParams(location: ReturnType) { const urlSearchParms = new URLSearchParams(location.search); @@ -18,15 +18,15 @@ function getQueryParams(location: ReturnType) { return queryParams; } -export function useUrlParams(route: Route) { +export function useUrlParams(route: typeof routes[T]) { const location = useLocation(); const pathParams = useParams(); const queryParams = getQueryParams(location); const { params } = route; const rts = { - queryRt: params?.query ? t.exact(params.query) : t.strict({}), - pathRt: params?.path ? t.exact(params.path) : t.strict({}), + queryRt: 'query' in params ? t.exact(params.query) : t.strict({}), + pathRt: 'path' in params ? t.exact(params.path) : t.strict({}), }; const queryResult = rts.queryRt.decode(queryParams); diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index e344847beb6d3..eb43b7ca8e95d 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -3,43 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { - EuiDatePicker, - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiSwitch, -} from '@elastic/eui'; -import moment from 'moment'; -import React, { useContext, useState } from 'react'; -import { ThemeContext } from 'styled-components'; +import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { isEmpty } from 'lodash'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; import { APMChart } from '../../components/chart/apm'; import { ChartContainer } from '../../components/chart/container'; import { MetricsChart } from '../../components/chart/metrics'; import { StackedBarChart } from '../../components/chart/stacked_bar'; import { EmptySection } from '../../components/empty_section'; import { WithHeaderLayout } from '../../components/layout/with_header'; -import { appsSection } from '../home/section'; +import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; import { getDataHandler } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; -import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; -import { useQueryParams } from '../../hooks/use_query_params'; -import { getParsedDate } from '../../utils/date'; -import { useKibanaUISettings, UI_SETTINGS } from '../../hooks/use_kibana_ui_settings'; +import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; import { RouteParams } from '../../routes'; +import { getParsedDate } from '../../utils/date'; +import { appsSection } from '../home/section'; interface Props { - routeParams?: RouteParams<'/overview'>; + routeParams: RouteParams<'/overview'>; } export const Overview = ({ routeParams }: Props) => { - console.log('### caue: Overview -> routeParams', routeParams?.params?.query); const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); - const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to } = useQueryParams(); + const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to } = routeParams.query; const { data = [] } = useFetcher(() => { const startTime = getParsedDate(rangeFrom); diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index d46d3bc9c8ad0..fd86a82eb4923 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -9,30 +9,17 @@ import { Home } from '../pages/home'; import { Overview } from '../pages/overview'; import { jsonRt } from './json_rt'; -export interface RouteParams { - params: DecodeParams; -} +export type RouteParams = DecodeParams; type DecodeParams = { [key in keyof TParams]: TParams[key] extends t.Any ? t.TypeOf : never; }; -type OverviewRouteParams = DecodeParams; -const overviewRouteParams: OverviewRouteParams = { query: {} }; -console.log('### caue:', overviewRouteParams); - export interface Params { - query?: t.Any; - path?: t.Any; -} -interface Routes { - [pathName: string]: { - handler: React.ReactNode; - params: Params; - }; + query?: t.HasProps; + path?: t.HasProps; } - -export const routes: Routes = { +export const routes = { '/': { handler: () => { return ; @@ -40,8 +27,9 @@ export const routes: Routes = { params: {}, }, '/overview': { - handler: ({ query, path }: { query: any; path: any }) => { - return ; + handler: ({ query }: { query: any }) => { + console.log('### caue: query', query); + return ; }, params: { query: t.partial({ From 28f26e23e62756aede1e0827c79a42e1e374954b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 23 Jun 2020 17:05:06 +0200 Subject: [PATCH 40/97] fixing imports and mock data --- .../public/components/chart/apm/index.tsx | 2 +- .../public/components/chart/metrics/index.tsx | 2 +- .../components/chart/stacked_bar/index.tsx | 2 +- .../{ => shared}/action_menu/index.tsx | 0 .../public/components/stats/index.tsx | 2 +- x-pack/plugins/observability/public/index.ts | 2 +- .../observability/public/mock/apm.mock.ts | 173 +-------------- .../observability/public/mock/logs.mock.ts | 210 ++---------------- .../observability/public/mock/metrics.mock.ts | 33 +-- .../observability/public/mock/uptime.mock.ts | 30 ++- .../public/typings/data_handler/index.d.ts | 42 ---- 11 files changed, 64 insertions(+), 434 deletions(-) rename x-pack/plugins/observability/public/components/{ => shared}/action_menu/index.tsx (100%) delete mode 100644 x-pack/plugins/observability/public/typings/data_handler/index.d.ts diff --git a/x-pack/plugins/observability/public/components/chart/apm/index.tsx b/x-pack/plugins/observability/public/components/chart/apm/index.tsx index 0a4ee9f11da1f..c7a11487b36e1 100644 --- a/x-pack/plugins/observability/public/components/chart/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/apm/index.tsx @@ -20,7 +20,7 @@ import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { FetchDataResponse } from '../../../typings/data_handler'; +import { FetchDataResponse } from '../../../typings/fetch_data_response'; interface Props { data?: FetchDataResponse; diff --git a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx index 032724109d78e..ca4b0a8a683e5 100644 --- a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/metrics/index.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { FetchDataResponse } from '../../../typings/data_handler'; +import { FetchDataResponse } from '../../../typings/fetch_data_response'; interface Props { data?: FetchDataResponse; diff --git a/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx b/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx index c05e16a544a97..40ee08c450eea 100644 --- a/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx +++ b/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx @@ -18,7 +18,7 @@ import { import { ThemeContext } from 'styled-components'; import { euiPaletteColorBlind } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { FetchDataResponse } from '../../../typings/data_handler'; +import { FetchDataResponse } from '../../../typings/fetch_data_response'; import { ChartContainer } from '../container'; import { Stats } from '../../stats'; diff --git a/x-pack/plugins/observability/public/components/action_menu/index.tsx b/x-pack/plugins/observability/public/components/shared/action_menu/index.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/action_menu/index.tsx rename to x-pack/plugins/observability/public/components/shared/action_menu/index.tsx diff --git a/x-pack/plugins/observability/public/components/stats/index.tsx b/x-pack/plugins/observability/public/components/stats/index.tsx index 22d4a1591e538..1df81f6da59c9 100644 --- a/x-pack/plugins/observability/public/components/stats/index.tsx +++ b/x-pack/plugins/observability/public/components/stats/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import React from 'react'; -import { Stat } from '../../typings/data_handler'; +import { Stat } from '../../typings/fetch_data_response'; interface Props { stats: Stat[]; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 2f72dad725b77..5b13383c5cb4a 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -13,7 +13,7 @@ export const plugin: PluginInitializer { +export const fetchApmData: FetchData = () => { return Promise.resolve(response); }; -const response: FetchDataResponse = { +const response: ApmFetchDataResponse = { title: i18n.translate('apm.observabilityDashboard.title', { defaultMessage: 'APM', }), appLink: '/app/apm', - stats: [{ label: 'Services', value: 11 }], - series: [ - { - key: 'transactions', + stats: { + services: { label: 'Services', value: 11 }, + transactions: { label: 'Transactions', value: 312000, color: 'euiColorVis1' }, + }, + series: { + transactions: { label: i18n.translate('apm.observabilityDashboard.chart.transactions', { defaultMessage: 'Transactions', }), @@ -172,159 +175,5 @@ const response: FetchDataResponse = { { x: 1591452000000, y: 9 }, ], }, - // { - // key: 'errors', - // label: i18n.translate('apm.observabilityDashboard.chart.errors', { - // defaultMessage: 'Errors', - // }), - // color: 'euiColorVis5', - // coordinates: [ - // { x: 1591365600000, y: 5 }, - // { x: 1591366200000, y: 4 }, - // { x: 1591366800000, y: 1 }, - // { x: 1591367400000, y: 5 }, - // { x: 1591368000000, y: 8 }, - // { x: 1591368600000, y: 4 }, - // { x: 1591369200000, y: 3 }, - // { x: 1591369800000, y: 6 }, - // { x: 1591370400000, y: 9 }, - // { x: 1591371000000, y: 1 }, - // { x: 1591371600000, y: 7 }, - // { x: 1591372200000, y: 4 }, - // { x: 1591372800000, y: 8 }, - // { x: 1591373400000, y: 6 }, - // { x: 1591374000000, y: 3 }, - // { x: 1591374600000, y: 5 }, - // { x: 1591375200000, y: 7 }, - // { x: 1591375800000, y: 5 }, - // { x: 1591376400000, y: 6 }, - // { x: 1591377000000, y: 4 }, - // { x: 1591377600000, y: 4 }, - // { x: 1591378200000, y: 7 }, - // { x: 1591378800000, y: 5 }, - // { x: 1591379400000, y: 7 }, - // { x: 1591380000000, y: 6 }, - // { x: 1591380600000, y: 3 }, - // { x: 1591381200000, y: 4 }, - // { x: 1591381800000, y: 5 }, - // { x: 1591382400000, y: 4 }, - // { x: 1591383000000, y: 8 }, - // { x: 1591383600000, y: 5 }, - // { x: 1591384200000, y: 5 }, - // { x: 1591384800000, y: 4 }, - // { x: 1591385400000, y: 5 }, - // { x: 1591386000000, y: 5 }, - // { x: 1591386600000, y: 7 }, - // { x: 1591387200000, y: 5 }, - // { x: 1591387800000, y: 6 }, - // { x: 1591388400000, y: 6 }, - // { x: 1591389000000, y: 4 }, - // { x: 1591389600000, y: 1 }, - // { x: 1591390200000, y: 6 }, - // { x: 1591390800000, y: 7 }, - // { x: 1591391400000, y: 5 }, - // { x: 1591392000000, y: 6 }, - // { x: 1591392600000, y: 5 }, - // { x: 1591393200000, y: 3 }, - // { x: 1591393800000, y: 3 }, - // { x: 1591394400000, y: 4 }, - // { x: 1591395000000, y: 2 }, - // { x: 1591395600000, y: 4 }, - // { x: 1591396200000, y: 3 }, - // { x: 1591396800000, y: 8 }, - // { x: 1591397400000, y: 5 }, - // { x: 1591398000000, y: 4 }, - // { x: 1591398600000, y: 4 }, - // { x: 1591399200000, y: 3 }, - // { x: 1591399800000, y: 7 }, - // { x: 1591400400000, y: 4 }, - // { x: 1591401000000, y: 4 }, - // { x: 1591401600000, y: 3 }, - // { x: 1591402200000, y: 3 }, - // { x: 1591402800000, y: 1 }, - // { x: 1591403400000, y: 5 }, - // { x: 1591404000000, y: 7 }, - // { x: 1591404600000, y: 5 }, - // { x: 1591405200000, y: 6 }, - // { x: 1591405800000, y: 3 }, - // { x: 1591406400000, y: 5 }, - // { x: 1591407000000, y: 5 }, - // { x: 1591407600000, y: 0 }, - // { x: 1591408200000, y: 4 }, - // { x: 1591408800000, y: 9 }, - // { x: 1591409400000, y: 5 }, - // { x: 1591410000000, y: 6 }, - // { x: 1591410600000, y: 4 }, - // { x: 1591411200000, y: 5 }, - // { x: 1591411800000, y: 5 }, - // { x: 1591412400000, y: 2 }, - // { x: 1591413000000, y: 4 }, - // { x: 1591413600000, y: 8 }, - // { x: 1591414200000, y: 5 }, - // { x: 1591414800000, y: 4 }, - // { x: 1591415400000, y: 4 }, - // { x: 1591416000000, y: 3 }, - // { x: 1591416600000, y: 7 }, - // { x: 1591417200000, y: 7 }, - // { x: 1591417800000, y: 6 }, - // { x: 1591418400000, y: 5 }, - // { x: 1591419000000, y: 5 }, - // { x: 1591419600000, y: 7 }, - // { x: 1591420200000, y: 5 }, - // { x: 1591420800000, y: 2 }, - // { x: 1591421400000, y: 3 }, - // { x: 1591422000000, y: 8 }, - // { x: 1591422600000, y: 5 }, - // { x: 1591423200000, y: 5 }, - // { x: 1591423800000, y: 1 }, - // { x: 1591424400000, y: 3 }, - // { x: 1591425000000, y: 5 }, - // { x: 1591425600000, y: 4 }, - // { x: 1591426200000, y: 8 }, - // { x: 1591426800000, y: 8 }, - // { x: 1591427400000, y: 5 }, - // { x: 1591428000000, y: 3 }, - // { x: 1591428600000, y: 4 }, - // { x: 1591429200000, y: 3 }, - // { x: 1591429800000, y: 5 }, - // { x: 1591430400000, y: 3 }, - // { x: 1591431000000, y: 4 }, - // { x: 1591431600000, y: 4 }, - // { x: 1591432200000, y: 5 }, - // { x: 1591432800000, y: 1 }, - // { x: 1591433400000, y: 4 }, - // { x: 1591434000000, y: 4 }, - // { x: 1591434600000, y: 9 }, - // { x: 1591435200000, y: 4 }, - // { x: 1591435800000, y: 4 }, - // { x: 1591436400000, y: 3 }, - // { x: 1591437000000, y: 6 }, - // { x: 1591437600000, y: 1 }, - // { x: 1591438200000, y: 5 }, - // { x: 1591438800000, y: 4 }, - // { x: 1591439400000, y: 4 }, - // { x: 1591440000000, y: 7 }, - // { x: 1591440600000, y: 5 }, - // { x: 1591441200000, y: 6 }, - // { x: 1591441800000, y: 6 }, - // { x: 1591442400000, y: 4 }, - // { x: 1591443000000, y: 5 }, - // { x: 1591443600000, y: 3 }, - // { x: 1591444200000, y: 3 }, - // { x: 1591444800000, y: 4 }, - // { x: 1591445400000, y: 2 }, - // { x: 1591446000000, y: 6 }, - // { x: 1591446600000, y: 4 }, - // { x: 1591447200000, y: 5 }, - // { x: 1591447800000, y: 2 }, - // { x: 1591448400000, y: 2 }, - // { x: 1591449000000, y: 4 }, - // { x: 1591449600000, y: 5 }, - // { x: 1591450200000, y: 2 }, - // { x: 1591450800000, y: 2 }, - // { x: 1591451400000, y: 1 }, - // { x: 1591452000000, y: 2 }, - // ], - // }, - ], + }, }; diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index 7e219d716eef9..2ce09b42267ed 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -5,25 +5,26 @@ */ import { i18n } from '@kbn/i18n'; -import { FetchDataResponse, FetchData } from '../typings/data_handler'; +import { LogsFetchDataResponse } from '../typings/fetch_data_response'; +import { FetchData } from '../data_handler'; -export const fetchLogsData: FetchData = () => { +export const fetchLogsData: FetchData = () => { return Promise.resolve(response); }; -const response: FetchDataResponse = { +const response: LogsFetchDataResponse = { title: i18n.translate('xpack.logs.observabilityDashboard.title', { defaultMessage: 'Logs', }), appLink: '/app/logs', - stats: [ - { label: 'unknown', value: 73777.20801439178 }, - { label: 'kibana.log', value: 1018.4591611479029 }, - { label: 'nginx.access', value: 5528.920109324083 }, - ], - series: [ - { - label: 'unknown', + stats: { + unknown: { label: 'Unknown', value: 73777 }, + 'kibana.log': { label: 'Kibana.log', value: 1018 }, + 'nginx.access': { label: 'Nginx.access', value: 5528 }, + }, + series: { + unknown: { + label: 'Unknwon', coordinates: [ { x: 1588942800000, @@ -63,8 +64,8 @@ const response: FetchDataResponse = { }, ], }, - { - label: 'kibana.log', + 'kibana.log': { + label: 'Kibana.log', coordinates: [ { x: 1588942800000, @@ -104,8 +105,8 @@ const response: FetchDataResponse = { }, ], }, - { - label: 'nginx.access', + 'nginx.access': { + label: 'Nginx.access', coordinates: [ { x: 1588942800000, @@ -145,182 +146,5 @@ const response: FetchDataResponse = { }, ], }, - { - label: 'nginx.error', - coordinates: [ - { - x: 1588942800000, - y: 134.79365079365078, - }, - { - x: 1589078700000, - y: 135.9205298013245, - }, - { - x: 1589214600000, - y: 123.67549668874172, - }, - { - x: 1589350500000, - y: 126.11920529801324, - }, - { - x: 1589486400000, - y: 144.1523178807947, - }, - { - x: 1589622300000, - y: 144.74834437086093, - }, - { - x: 1589758200000, - y: 144.40397350993376, - }, - { - x: 1589894100000, - y: 12.675496688741722, - }, - { - x: 1590030000000, - y: 0, - }, - ], - }, - { - label: 'postgresql.log', - coordinates: [ - { - x: 1588942800000, - y: 0.1935483870967742, - }, - { - x: 1589078700000, - y: 0.4370860927152318, - }, - { - x: 1589214600000, - y: 0.3973509933774834, - }, - { - x: 1589350500000, - y: 0.33112582781456956, - }, - { - x: 1589486400000, - y: 0.2980132450331126, - }, - { - x: 1589622300000, - y: 0.3973509933774834, - }, - { - x: 1589758200000, - y: 0.4768211920529801, - }, - { - x: 1589894100000, - y: 0.09271523178807947, - }, - { - x: 1590030000000, - y: 0, - }, - ], - }, - { - label: 'redis.log', - coordinates: [ - { - x: 1588942800000, - y: 11.714285714285714, - }, - { - x: 1589078700000, - y: 5.880794701986755, - }, - { - x: 1589214600000, - y: 0.4304635761589404, - }, - { - x: 1589350500000, - y: 0.4370860927152318, - }, - { - x: 1589486400000, - y: 0.41721854304635764, - }, - { - x: 1589622300000, - y: 0.423841059602649, - }, - { - x: 1589758200000, - y: 0.271523178807947, - }, - { - x: 1589894100000, - y: 0.3708609271523179, - }, - { - x: 1590030000000, - y: 0, - }, - ], - }, - { - label: 'redis.slowlog', - coordinates: [ - { - x: 1588942800000, - y: 11.714285714285714, - }, - { - x: 1589078700000, - y: 5.880794701986755, - }, - { - x: 1589214600000, - y: 0.4304635761589404, - }, - { - x: 1589350500000, - y: 0.4370860927152318, - }, - { - x: 1589486400000, - y: 0.41721854304635764, - }, - { - x: 1589622300000, - y: 0.423841059602649, - }, - { - x: 1589758200000, - y: 0.271523178807947, - }, - { - x: 1589894100000, - y: 0.3973509933774834, - }, - { - x: 1590030000000, - y: 0, - }, - ], - }, - { - label: 'haproxy.log', - coordinates: [ - { - x: 1589894100000, - y: 0, - }, - { - x: 1590030000000, - y: 0, - }, - ], - }, - ], + }, }; diff --git a/x-pack/plugins/observability/public/mock/metrics.mock.ts b/x-pack/plugins/observability/public/mock/metrics.mock.ts index 14eb09fc985b5..8d3d1973cdb45 100644 --- a/x-pack/plugins/observability/public/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/mock/metrics.mock.ts @@ -5,26 +5,29 @@ */ import { i18n } from '@kbn/i18n'; -import { FetchDataResponse, FetchData } from '../typings/data_handler'; +import { MetricsFetchDataResponse } from '../typings/fetch_data_response'; +import { FetchData } from '../data_handler'; -export const fetchMetricsData: FetchData = () => { +export const fetchMetricsData: FetchData = () => { return Promise.resolve(response); }; -const response: FetchDataResponse = { +const response: MetricsFetchDataResponse = { title: i18n.translate('metrics.observabilityDashboard.title', { defaultMessage: 'Metrics', }), appLink: '/app/apm', - stats: [ - { label: 'Hosts', value: 11 }, - { label: 'CPU usage', value: 80 }, - { label: 'Memory Usage', value: 36.2 }, - { label: 'Disk Usage', value: 32.4 }, - ], - series: [ - { - label: 'Outbount trafic', + stats: { + hosts: { label: 'Hosts', value: 11 }, + cpu: { label: 'CPU usage', pct: 80 }, + memory: { label: 'Memory Usage', pct: 36.2 }, + disk: { label: 'Disk Usage', pct: 32.4 }, + inboundTraffic: { label: 'Inbount traffic', bytes: 1024 }, + outboundTraffic: { label: 'Outbount traffic', bytes: 1024 }, + }, + series: { + outboundTraffic: { + label: 'Outbount traffic', coordinates: [ { x: 1589805437549, @@ -68,8 +71,8 @@ const response: FetchDataResponse = { }, ], }, - { - label: 'Inbound trafic', + inboundTraffic: { + label: 'Inbound traffic', coordinates: [ { x: 1589805437549, @@ -113,5 +116,5 @@ const response: FetchDataResponse = { }, ], }, - ], + }, }; diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts index 9783143bd7864..c3157af405a43 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -3,27 +3,23 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { FetchDataResponse, FetchData } from '../typings/data_handler'; +import { UptimeFetchDataResponse } from '../typings/fetch_data_response'; +import { FetchData } from '../data_handler'; -export const fetchUptimeData: FetchData = () => { +export const fetchUptimeData: FetchData = () => { return Promise.resolve(response); }; -const response: FetchDataResponse = { +const response: UptimeFetchDataResponse = { title: 'Uptiome', appLink: '/app/uptime', - stats: [ - { - label: 'Down', - value: 115, - }, - { - label: 'Up', - value: 582, - }, - ], - series: [ - { + stats: { + monitors: { label: 'Monitors', value: 5 }, + down: { label: 'Down', value: 115 }, + up: { label: 'Up', value: 582 }, + }, + series: { + down: { label: 'Down', color: 'euiColorVis2', coordinates: [ @@ -93,7 +89,7 @@ const response: FetchDataResponse = { }, ], }, - { + up: { label: 'Up', color: 'euiColorLightShade', coordinates: [ @@ -163,5 +159,5 @@ const response: FetchDataResponse = { }, ], }, - ], + }, }; diff --git a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts b/x-pack/plugins/observability/public/typings/data_handler/index.d.ts deleted file mode 100644 index 93461f8ee395c..0000000000000 --- a/x-pack/plugins/observability/public/typings/data_handler/index.d.ts +++ /dev/null @@ -1,42 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export interface Stat { - label: string; - value: number; - color?: string; -} - -export interface Coordinates { - x: number; - y?: number; -} - -interface Series { - label: string; - coordinates: Coordinates[]; - color?: string; - key?: string; -} - -export interface FetchDataResponse { - title: string; - appLink: string; - stats: Stat[]; - series: Series[]; -} -interface FetchDataParams { - // The start timestamp in milliseconds of the queried time interval - startTime: string; - // The end timestamp in milliseconds of the queried time interval - endTime: string; - // The aggregation bucket size in milliseconds if applicable to the data source - bucketSize: string; -} - -export type FetchData = (fetchDataParams: FetchDataParams) => Promise; - -export type HasData = () => Promise; From 6e511e142865bcc4d15fd7506843e076babf3b44 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 23 Jun 2020 17:11:07 +0200 Subject: [PATCH 41/97] adding app folder --- .../public/components/{ => app}/chart/apm/index.tsx | 2 +- .../public/components/{ => app}/chart/container.tsx | 0 .../components/{ => app}/chart/metrics/index.tsx | 2 +- .../components/{ => app}/chart/stacked_bar/index.tsx | 2 +- .../components/{ => app}/empty_section/index.tsx | 3 ++- .../public/components/{ => app}/header/index.tsx | 0 .../components/{ => app}/layout/with_header.tsx | 0 .../public/components/{ => app}/stats/index.tsx | 2 +- .../public/components/shared/data_picker/index.tsx | 1 - .../observability/public/pages/home/index.tsx | 2 +- .../observability/public/pages/overview/index.tsx | 12 ++++++------ 11 files changed, 13 insertions(+), 13 deletions(-) rename x-pack/plugins/observability/public/components/{ => app}/chart/apm/index.tsx (97%) rename x-pack/plugins/observability/public/components/{ => app}/chart/container.tsx (100%) rename x-pack/plugins/observability/public/components/{ => app}/chart/metrics/index.tsx (96%) rename x-pack/plugins/observability/public/components/{ => app}/chart/stacked_bar/index.tsx (98%) rename x-pack/plugins/observability/public/components/{ => app}/empty_section/index.tsx (93%) rename x-pack/plugins/observability/public/components/{ => app}/header/index.tsx (100%) rename x-pack/plugins/observability/public/components/{ => app}/layout/with_header.tsx (100%) rename x-pack/plugins/observability/public/components/{ => app}/stats/index.tsx (93%) diff --git a/x-pack/plugins/observability/public/components/chart/apm/index.tsx b/x-pack/plugins/observability/public/components/app/chart/apm/index.tsx similarity index 97% rename from x-pack/plugins/observability/public/components/chart/apm/index.tsx rename to x-pack/plugins/observability/public/components/app/chart/apm/index.tsx index c7a11487b36e1..e18cd2ba0786e 100644 --- a/x-pack/plugins/observability/public/components/chart/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart/apm/index.tsx @@ -20,7 +20,7 @@ import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { FetchDataResponse } from '../../../typings/fetch_data_response'; +import { FetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { data?: FetchDataResponse; diff --git a/x-pack/plugins/observability/public/components/chart/container.tsx b/x-pack/plugins/observability/public/components/app/chart/container.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/chart/container.tsx rename to x-pack/plugins/observability/public/components/app/chart/container.tsx diff --git a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/chart/metrics/index.tsx similarity index 96% rename from x-pack/plugins/observability/public/components/chart/metrics/index.tsx rename to x-pack/plugins/observability/public/components/app/chart/metrics/index.tsx index ca4b0a8a683e5..deefac3515d4d 100644 --- a/x-pack/plugins/observability/public/components/chart/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart/metrics/index.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { ChartContainer } from '../container'; -import { FetchDataResponse } from '../../../typings/fetch_data_response'; +import { FetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { data?: FetchDataResponse; diff --git a/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx b/x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx similarity index 98% rename from x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx rename to x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx index 40ee08c450eea..1dfe3df8cef83 100644 --- a/x-pack/plugins/observability/public/components/chart/stacked_bar/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx @@ -18,7 +18,7 @@ import { import { ThemeContext } from 'styled-components'; import { euiPaletteColorBlind } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { FetchDataResponse } from '../../../typings/fetch_data_response'; +import { FetchDataResponse } from '../../../../typings/fetch_data_response'; import { ChartContainer } from '../container'; import { Stats } from '../../stats'; diff --git a/x-pack/plugins/observability/public/components/empty_section/index.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx similarity index 93% rename from x-pack/plugins/observability/public/components/empty_section/index.tsx rename to x-pack/plugins/observability/public/components/app/empty_section/index.tsx index 73d7cb4b0d89b..5a8775feb66f4 100644 --- a/x-pack/plugins/observability/public/components/empty_section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx @@ -8,7 +8,8 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { EuiButton } from '@elastic/eui'; import { ThemeContext } from 'styled-components'; import { EuiText } from '@elastic/eui'; -import { ISection } from '../../pages/home/section'; +// TODO: caue fix it +import { ISection } from '../../../pages/home/section'; interface Props { section: ISection; diff --git a/x-pack/plugins/observability/public/components/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/header/index.tsx rename to x-pack/plugins/observability/public/components/app/header/index.tsx diff --git a/x-pack/plugins/observability/public/components/layout/with_header.tsx b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/layout/with_header.tsx rename to x-pack/plugins/observability/public/components/app/layout/with_header.tsx diff --git a/x-pack/plugins/observability/public/components/stats/index.tsx b/x-pack/plugins/observability/public/components/app/stats/index.tsx similarity index 93% rename from x-pack/plugins/observability/public/components/stats/index.tsx rename to x-pack/plugins/observability/public/components/app/stats/index.tsx index 1df81f6da59c9..bd9a94d99c36c 100644 --- a/x-pack/plugins/observability/public/components/stats/index.tsx +++ b/x-pack/plugins/observability/public/components/app/stats/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import React from 'react'; -import { Stat } from '../../typings/fetch_data_response'; +import { Stat } from '../../../typings/fetch_data_response'; interface Props { stats: Stat[]; diff --git a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx index f206d583270bc..050fd522e25eb 100644 --- a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx @@ -5,7 +5,6 @@ */ import { EuiSuperDatePicker } from '@elastic/eui'; -import { stringify } from 'query-string'; import React from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings'; diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 3524d869e43a1..625ad376d3fe2 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -21,7 +21,7 @@ import React, { useEffect, useContext } from 'react'; import styled, { ThemeContext } from 'styled-components'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { appsSection } from './section'; -import { WithHeaderLayout } from '../../components/layout/with_header'; +import { WithHeaderLayout } from '../../components/app/layout/with_header'; const EuiCardWithoutPadding = styled(EuiCard)` padding: 0; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index eb43b7ca8e95d..0ae481874cfcb 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -7,12 +7,12 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui' import { isEmpty } from 'lodash'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { APMChart } from '../../components/chart/apm'; -import { ChartContainer } from '../../components/chart/container'; -import { MetricsChart } from '../../components/chart/metrics'; -import { StackedBarChart } from '../../components/chart/stacked_bar'; -import { EmptySection } from '../../components/empty_section'; -import { WithHeaderLayout } from '../../components/layout/with_header'; +import { APMChart } from '../../components/app/chart/apm'; +import { ChartContainer } from '../../components/app/chart/container'; +import { MetricsChart } from '../../components/app/chart/metrics'; +import { StackedBarChart } from '../../components/app/chart/stacked_bar'; +import { EmptySection } from '../../components/app/empty_section'; +import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; import { getDataHandler } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; From 215ca59fa534f3fdef02ff7c403a5a234db80064 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 23 Jun 2020 20:03:02 +0200 Subject: [PATCH 42/97] creating a section for each plugin --- .../app/chart/stacked_bar/index.tsx | 137 ------------------ .../app/{chart => section}/apm/index.tsx | 39 ++--- .../container.tsx => section/index.tsx} | 2 +- .../components/app/section/logs/index.tsx | 99 +++++++++++++ .../app/{chart => section}/metrics/index.tsx | 19 +-- .../components/app/section/uptime/index.tsx | 96 ++++++++++++ .../observability/public/data_handler.ts | 21 +-- .../observability/public/mock/uptime.mock.ts | 2 +- .../public/pages/overview/index.tsx | 31 ++-- 9 files changed, 242 insertions(+), 204 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx rename x-pack/plugins/observability/public/components/app/{chart => section}/apm/index.tsx (66%) rename x-pack/plugins/observability/public/components/app/{chart/container.tsx => section/index.tsx} (92%) create mode 100644 x-pack/plugins/observability/public/components/app/section/logs/index.tsx rename x-pack/plugins/observability/public/components/app/{chart => section}/metrics/index.tsx (77%) create mode 100644 x-pack/plugins/observability/public/components/app/section/uptime/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx b/x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx deleted file mode 100644 index 1dfe3df8cef83..0000000000000 --- a/x-pack/plugins/observability/public/components/app/chart/stacked_bar/index.tsx +++ /dev/null @@ -1,137 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import React, { useContext, Fragment } from 'react'; -import { - Chart, - Settings, - DARK_THEME, - LIGHT_THEME, - BarSeries, - ScaleType, - Axis, - Position, - niceTimeFormatter, -} from '@elastic/charts'; -import { ThemeContext } from 'styled-components'; -import { euiPaletteColorBlind } from '@elastic/eui'; -import numeral from '@elastic/numeral'; -import { FetchDataResponse } from '../../../../typings/fetch_data_response'; -import { ChartContainer } from '../container'; -import { Stats } from '../../stats'; - -interface Props { - data?: FetchDataResponse; -} - -export const StackedBarChart = ({ data }: Props) => { - const theme = useContext(ThemeContext); - - if (!data) { - return null; - } - - const customColors = { - colors: { - vizColors: euiPaletteColorBlind({ rotations: Math.ceil(data.series.length / 10) }), - }, - }; - - const min = data.series[0]?.coordinates[0].x || 0; - const max = data.series[0]?.coordinates[data.series[0]?.coordinates.length - 1].x || 0; - const formatter = niceTimeFormatter([min, max]); - - const getSerieColor = (color?: string) => { - if (color) { - return theme.eui[color]; - } - }; - - return ( - - numeral(value).format('0a')} /> - - { - console.log('#### Logs', x); - }} - theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} - showLegend - legendPosition="bottom" - xDomain={{ min, max }} - /> - {data.series.map((serie) => { - return ( - - - - numeral(d).format('0a')} - /> - - ); - })} - - - ); -}; - -{ - /* - - { - console.log('#### Logs', x); - }} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min: startAPM, max: endAPM }} - /> - - numeral(d).format('0a')} - /> - - - - */ -} diff --git a/x-pack/plugins/observability/public/components/app/chart/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx similarity index 66% rename from x-pack/plugins/observability/public/components/app/chart/apm/index.tsx rename to x-pack/plugins/observability/public/components/app/section/apm/index.tsx index e18cd2ba0786e..51f0a4886b3ff 100644 --- a/x-pack/plugins/observability/public/components/app/chart/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -19,22 +19,21 @@ import { EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { ChartContainer } from '../container'; -import { FetchDataResponse } from '../../../../typings/fetch_data_response'; +import { SectionContainer } from '../'; +import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { - data?: FetchDataResponse; + data?: ApmFetchDataResponse; } -export const APMChart = ({ data }: Props) => { +export const APMSection = ({ data }: Props) => { const theme = useContext(ThemeContext); if (!data) { return null; } - const transactionSeries = data.series.find((d) => d.key === 'transactions'); - const errorSeries = data.series.find((d) => d.key === 'errors'); + const transactionSeries = data.series.transactions; const startAPM = transactionSeries?.coordinates[0].x || 0; const endAPM = transactionSeries?.coordinates[transactionSeries?.coordinates.length - 1].x || 0; @@ -47,10 +46,10 @@ export const APMChart = ({ data }: Props) => { }; return ( - - {data.stats.map((stat) => ( + + {/* {data.stats.map((stat) => ( - ))} + ))} */} { @@ -81,28 +80,8 @@ export const APMChart = ({ data }: Props) => { /> )} - {errorSeries?.coordinates && ( - <> - - `${Number(d).toFixed(0)} %`} - groupId="errors" - /> - - )} - + ); }; diff --git a/x-pack/plugins/observability/public/components/app/chart/container.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx similarity index 92% rename from x-pack/plugins/observability/public/components/app/chart/container.tsx rename to x-pack/plugins/observability/public/components/app/section/index.tsx index 5127317129087..1a321e51b2278 100644 --- a/x-pack/plugins/observability/public/components/app/chart/container.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -13,7 +13,7 @@ interface Props { children: React.ReactNode; } -export const ChartContainer = ({ title, appLink, children }: Props) => { +export const SectionContainer = ({ title, appLink, children }: Props) => { return ( { + const theme = useContext(ThemeContext); + + if (!data) { + return null; + } + + const xCoordinates = Object.values(data.series) + .map((serie) => serie.coordinates.map((coordinate) => coordinate.x)) + .flatMap((_) => _); + + const min = d3.min(xCoordinates); + const max = d3.max(xCoordinates); + + const formatter = niceTimeFormatter([min, max]); + + const customColors = { + colors: { + vizColors: euiPaletteColorBlind({ + rotations: Math.ceil(Object.keys(data.series).length / 10), + }), + }, + }; + + return ( + + {/* numeral(value).format('0a')} /> */} + + { + console.log('#### Logs', x); + }} + theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} + showLegend + legendPosition="bottom" + xDomain={{ min, max }} + /> + {Object.values(data.series).map((serie) => { + return ( + + + + numeral(d).format('0a')} + /> + + ); + })} + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/chart/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx similarity index 77% rename from x-pack/plugins/observability/public/components/app/chart/metrics/index.tsx rename to x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index deefac3515d4d..cfeef4ce08bec 100644 --- a/x-pack/plugins/observability/public/components/app/chart/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -8,14 +8,14 @@ import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { ChartContainer } from '../container'; -import { FetchDataResponse } from '../../../../typings/fetch_data_response'; +import { SectionContainer } from '../'; +import { MetricsFetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { - data?: FetchDataResponse; + data?: MetricsFetchDataResponse; } -export const MetricsChart = ({ data }: Props) => { +export const MetricsSection = ({ data }: Props) => { const theme = useContext(ThemeContext); if (!data) { @@ -23,9 +23,9 @@ export const MetricsChart = ({ data }: Props) => { } return ( - + - {data.stats.map((stat) => { + {/* {data.stats.map((stat) => { return ( @@ -33,8 +33,9 @@ export const MetricsChart = ({ data }: Props) => { ); - })} - {data.series.map((serie) => { + })} */} + {Object.keys(data.series).map((key) => { + const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; return ( @@ -58,6 +59,6 @@ export const MetricsChart = ({ data }: Props) => { ); })} - + ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx new file mode 100644 index 0000000000000..72b44738ab2ff --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useContext } from 'react'; +import d3 from 'd3'; +import { + Chart, + Settings, + BarSeries, + Axis, + Position, + DARK_THEME, + LIGHT_THEME, + ScaleType, + niceTimeFormatter, +} from '@elastic/charts'; +import { ThemeContext } from 'styled-components'; +import numeral from '@elastic/numeral'; +import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { SectionContainer } from '../'; + +interface Props { + data?: UptimeFetchDataResponse; +} + +export const UptimeSection = ({ data }: Props) => { + const theme = useContext(ThemeContext); + + if (!data) { + return null; + } + + const xCoordinates = Object.values(data.series) + .map((serie) => serie.coordinates.map((coordinate) => coordinate.x)) + .flatMap((_) => _); + + const min = d3.min(xCoordinates); + const max = d3.max(xCoordinates); + + const formatter = niceTimeFormatter([min, max]); + + const getSerieColor = (color?: string) => { + if (color) { + return theme.eui[color]; + } + }; + + return ( + + {/* numeral(value).format('0a')} /> */} + + { + console.log('#### Logs', x); + }} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min, max }} + /> + {Object.values(data.series).map((serie) => { + return ( + + + + numeral(d).format('0a')} + /> + + ); + })} + + + ); +}; diff --git a/x-pack/plugins/observability/public/data_handler.ts b/x-pack/plugins/observability/public/data_handler.ts index 8f80f79b2e829..5ec74bc11245c 100644 --- a/x-pack/plugins/observability/public/data_handler.ts +++ b/x-pack/plugins/observability/public/data_handler.ts @@ -21,23 +21,26 @@ export type FetchData = ( ) => Promise; export type HasData = () => Promise; -interface DataHandler { - fetchData: FetchData; +interface DataHandler { + fetchData: FetchData; hasData: HasData; } const dataHandlers: Partial> = {}; -export type RegisterDataHandler = (params: { - appName: T; - fetchData: FetchData; - hasData: HasData; -}) => void; +export type RegisterDataHandler = ( + params: { + appName: T; + } & DataHandler +) => void; export const registerDataHandler: RegisterDataHandler = ({ appName, fetchData, hasData }) => { dataHandlers[appName] = { fetchData, hasData }; }; -export function getDataHandler(appName: ObservabilityApp): DataHandler | undefined { - return dataHandlers[appName]; +export function getDataHandler(appName: T) { + const dataHandler = dataHandlers[appName]; + if (dataHandler) { + return dataHandler as DataHandler; + } } diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts index c3157af405a43..c990ba8da4d70 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -11,7 +11,7 @@ export const fetchUptimeData: FetchData = () => { }; const response: UptimeFetchDataResponse = { - title: 'Uptiome', + title: 'Uptime', appLink: '/app/uptime', stats: { monitors: { label: 'Monitors', value: 5 }, diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 0ae481874cfcb..b0e2288ad902d 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -7,12 +7,12 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui' import { isEmpty } from 'lodash'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { APMChart } from '../../components/app/chart/apm'; -import { ChartContainer } from '../../components/app/chart/container'; -import { MetricsChart } from '../../components/app/chart/metrics'; -import { StackedBarChart } from '../../components/app/chart/stacked_bar'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; +import { APMSection } from '../../components/app/section/apm'; +import { LogsSection } from '../../components/app/section/logs'; +import { MetricsSection } from '../../components/app/section/metrics'; +import { UptimeSection } from '../../components/app/section/uptime'; import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; import { getDataHandler } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; @@ -36,12 +36,12 @@ export const Overview = ({ routeParams }: Props) => { const endTime = getParsedDate(rangeTo); if (startTime && endTime) { const params = { startTime, endTime, bucketSize: '3' }; - const apmHandler = getDataHandler('apm')?.fetchData(params); - const logsHandler = getDataHandler('infra_logs')?.fetchData(params); - const metricsHandler = getDataHandler('infra_metrics')?.fetchData(params); - const uptimeHandler = getDataHandler('uptime')?.fetchData(params); + const apmDataPromise = getDataHandler('apm')?.fetchData(params); + const logsDataPromise = getDataHandler('infra_logs')?.fetchData(params); + const metricsDataPromise = getDataHandler('infra_metrics')?.fetchData(params); + const uptimeDataPromise = getDataHandler('uptime')?.fetchData(params); - return Promise.all([apmHandler, logsHandler, metricsHandler, uptimeHandler]); + return Promise.all([apmDataPromise, logsDataPromise, metricsDataPromise, uptimeDataPromise]); } }, [rangeFrom, rangeTo]); @@ -76,24 +76,21 @@ export const Overview = ({ routeParams }: Props) => { - + - + - + - + - - chart goes here - + Alert chart goes here - From 513868d3cd061652d6f40f0b84e3c1aaaee28031 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 24 Jun 2020 08:31:59 +0200 Subject: [PATCH 43/97] adding stats --- .../components/app/section/apm/index.tsx | 18 +++-- .../components/app/section/logs/index.tsx | 17 ++++- .../components/app/section/metrics/index.tsx | 69 +++++++++++-------- .../components/app/section/uptime/index.tsx | 17 ++++- .../public/components/app/stats/index.tsx | 31 --------- .../typings/fetch_data_response/index.d.ts | 8 +-- .../public/utils/format_stat_value.test.ts | 30 ++++++++ .../public/utils/format_stat_value.ts | 28 ++++++++ 8 files changed, 143 insertions(+), 75 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/app/stats/index.tsx create mode 100644 x-pack/plugins/observability/public/utils/format_stat_value.test.ts create mode 100644 x-pack/plugins/observability/public/utils/format_stat_value.ts diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 51f0a4886b3ff..a15882accf799 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -10,15 +10,14 @@ import { Chart, DARK_THEME, LIGHT_THEME, - LineSeries, niceTimeFormatter, - Position, Settings, } from '@elastic/charts'; -import { EuiStat } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; +import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; @@ -47,9 +46,16 @@ export const APMSection = ({ data }: Props) => { return ( - {/* {data.stats.map((stat) => ( - - ))} */} + + {Object.keys(data.stats).map((key) => { + const stat = data.stats[key as keyof ApmFetchDataResponse['stats']]; + return ( + + + + ); + })} + { diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 63a2e7eced1d8..085dbf0564c23 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -21,8 +21,12 @@ import { import { ThemeContext } from 'styled-components'; import { euiPaletteColorBlind } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiStat } from '@elastic/eui'; +import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; +import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { data?: LogsFetchDataResponse; @@ -54,7 +58,16 @@ export const LogsSection = ({ data }: Props) => { return ( - {/* numeral(value).format('0a')} /> */} + + {Object.keys(data.stats).map((key) => { + const stat = data.stats[key as keyof LogsFetchDataResponse['stats']]; + return ( + + + + ); + })} + { diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index cfeef4ce08bec..3f42157aa0a8c 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -8,16 +8,15 @@ import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; +import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; -import { MetricsFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; interface Props { data?: MetricsFetchDataResponse; } export const MetricsSection = ({ data }: Props) => { - const theme = useContext(ThemeContext); - if (!data) { return null; } @@ -25,35 +24,23 @@ export const MetricsSection = ({ data }: Props) => { return ( - {/* {data.stats.map((stat) => { - return ( - - - - - - ); - })} */} - {Object.keys(data.series).map((key) => { + {Object.keys(data.stats).map((key) => { + const statKey = key as keyof MetricsFetchDataResponse['stats']; + const stat = data.stats[statKey]; + const value = formatStatValue(stat); + const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; + + const chart = serie ? ( + + ) : ( + + ); + return ( - - - - - - + + + {statKey !== 'hosts' && chart} ); @@ -62,3 +49,25 @@ export const MetricsSection = ({ data }: Props) => { ); }; + +const AreaChart = ({ serie }: { serie: Series }) => { + const theme = useContext(ThemeContext); + + return ( + + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 72b44738ab2ff..6a0a93941007b 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -19,8 +19,12 @@ import { } from '@elastic/charts'; import { ThemeContext } from 'styled-components'; import numeral from '@elastic/numeral'; -import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiStat } from '@elastic/eui'; +import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; +import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { data?: UptimeFetchDataResponse; @@ -50,7 +54,16 @@ export const UptimeSection = ({ data }: Props) => { return ( - {/* numeral(value).format('0a')} /> */} + + {Object.keys(data.stats).map((key) => { + const stat = data.stats[key as keyof UptimeFetchDataResponse['stats']]; + return ( + + + + ); + })} + { diff --git a/x-pack/plugins/observability/public/components/app/stats/index.tsx b/x-pack/plugins/observability/public/components/app/stats/index.tsx deleted file mode 100644 index bd9a94d99c36c..0000000000000 --- a/x-pack/plugins/observability/public/components/app/stats/index.tsx +++ /dev/null @@ -1,31 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; -import React from 'react'; -import { Stat } from '../../../typings/fetch_data_response'; - -interface Props { - stats: Stat[]; - formatter?: (value: number) => string; -} - -export const Stats = ({ stats, formatter }: Props) => { - return ( - - {stats.map((stat) => { - return ( - - - - ); - })} - - ); -}; diff --git a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts index 30ecb24a58a5a..685896394f206 100644 --- a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts +++ b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts @@ -4,17 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -interface Percentage { +export interface Percentage { label: string; pct: number; color?: string; } -interface Bytes { +export interface Bytes { label: string; bytes: number; color?: string; } -interface Numeral { +export interface Numeral { label: string; value: number; color?: string; @@ -25,7 +25,7 @@ export interface Coordinates { y?: number; } -interface Series { +export interface Series { label: string; coordinates: Coordinates[]; color?: string; diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.test.ts b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts new file mode 100644 index 0000000000000..3bb44a2756336 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { formatStatValue } from './format_stat_value'; + +describe('formatStatValue', () => { + it('formats value as numeral', () => { + const stat = { + label: 'numeral stat', + value: 1000, + }; + expect(formatStatValue(stat)).toEqual('1k'); + }); + it('formats value as bytes', () => { + const stat = { + label: 'bytes stat', + bytes: 11.4, + }; + expect(formatStatValue(stat)).toEqual('11.4 Mb/s'); + }); + it('formats value as percentage', () => { + const stat = { + label: 'bytes stat', + pct: 0.841, + }; + expect(formatStatValue(stat)).toEqual('84.1%'); + }); +}); diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.ts b/x-pack/plugins/observability/public/utils/format_stat_value.ts new file mode 100644 index 0000000000000..81c7ca62c064f --- /dev/null +++ b/x-pack/plugins/observability/public/utils/format_stat_value.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import numeral from '@elastic/numeral'; +import { Numeral, Percentage, Bytes } from '../typings/fetch_data_response'; + +type Stat = Numeral | Percentage | Bytes; + +function isBytes(stat: Stat): stat is Bytes { + return (stat as Bytes).bytes !== undefined; +} + +function isPercentage(stat: Stat): stat is Percentage { + return (stat as Percentage).pct !== undefined; +} + +export function formatStatValue(stat: Stat) { + if (isBytes(stat)) { + return `${stat.bytes} Mb/s`; + } else if (isPercentage(stat)) { + return numeral(stat.pct).format('0.0%'); + } else { + return numeral(stat.value).format('0a'); + } +} From c0bf62e91cdae77dd54edeb6ab877bdcb5f4913e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 24 Jun 2020 10:55:04 +0200 Subject: [PATCH 44/97] adding domain min max --- .../public/components/app/section/apm/index.tsx | 13 +++++++++---- .../public/components/app/section/logs/index.tsx | 1 - .../public/components/app/section/metrics/index.tsx | 5 ++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index a15882accf799..14bb638f7c796 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -13,6 +13,7 @@ import { niceTimeFormatter, Settings, } from '@elastic/charts'; +import d3 from 'd3'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; @@ -34,9 +35,12 @@ export const APMSection = ({ data }: Props) => { const transactionSeries = data.series.transactions; - const startAPM = transactionSeries?.coordinates[0].x || 0; - const endAPM = transactionSeries?.coordinates[transactionSeries?.coordinates.length - 1].x || 0; - const formatterAPM = niceTimeFormatter([startAPM, endAPM]); + const xCoordinates = transactionSeries.coordinates.map((coordinate) => coordinate.x); + + const min = d3.min(xCoordinates); + const max = d3.max(xCoordinates); + + const formatter = niceTimeFormatter([min, max]); const getSerieColor = (color?: string) => { if (color) { @@ -64,6 +68,7 @@ export const APMSection = ({ data }: Props) => { theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} showLegend={true} legendPosition="bottom" + xDomain={{ min, max }} /> {transactionSeries?.coordinates && ( <> @@ -86,7 +91,7 @@ export const APMSection = ({ data }: Props) => { /> )} - + ); diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 085dbf0564c23..7b04333e99dbb 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -6,7 +6,6 @@ import React, { useContext, Fragment } from 'react'; import d3 from 'd3'; - import { Chart, Settings, diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 3f42157aa0a8c..217f70f5e0236 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -3,14 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiProgress } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiStat } from '@elastic/eui'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; +import { formatStatValue } from '../../../../utils/format_stat_value'; interface Props { data?: MetricsFetchDataResponse; From c18a62a996297af55c192de1f73637ad3d52e6b1 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 24 Jun 2020 11:59:00 +0200 Subject: [PATCH 45/97] refactoring xcoordinaters --- .../public/components/app/section/logs/index.tsx | 8 ++++---- .../public/components/app/section/uptime/index.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 7b04333e99dbb..5b4523c4da9d4 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -38,9 +38,9 @@ export const LogsSection = ({ data }: Props) => { return null; } - const xCoordinates = Object.values(data.series) - .map((serie) => serie.coordinates.map((coordinate) => coordinate.x)) - .flatMap((_) => _); + const xCoordinates = Object.values(data.series).flatMap((serie) => + serie.coordinates.map((coordinate) => coordinate.x) + ); const min = d3.min(xCoordinates); const max = d3.max(xCoordinates); @@ -100,7 +100,7 @@ export const LogsSection = ({ data }: Props) => { id="y-axis" showGridLines position={Position.Left} - tickFormat={(d: any) => numeral(d).format('0a')} + tickFormat={(d: number) => numeral(d).format('0a')} /> ); diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 6a0a93941007b..c754e38a800e6 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -37,9 +37,9 @@ export const UptimeSection = ({ data }: Props) => { return null; } - const xCoordinates = Object.values(data.series) - .map((serie) => serie.coordinates.map((coordinate) => coordinate.x)) - .flatMap((_) => _); + const xCoordinates = Object.values(data.series).flatMap((serie) => + serie.coordinates.map((coordinate) => coordinate.x) + ); const min = d3.min(xCoordinates); const max = d3.max(xCoordinates); From e451893ac237e96e3c12cec4eb064bd25fd85e5a Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 24 Jun 2020 18:41:44 +0200 Subject: [PATCH 46/97] fixing route --- .../public/application/index.tsx | 18 +++----- .../observability/public/mock/apm.mock.ts | 4 +- .../observability/public/mock/logs.mock.ts | 6 +-- .../observability/public/mock/metrics.mock.ts | 12 +++--- .../observability/public/mock/uptime.mock.ts | 6 +-- .../observability/public/pages/home/index.tsx | 4 +- .../observability/public/routes/index.tsx | 16 +------- .../public/utils/format_stat_value.test.ts | 41 ++++++++++++++----- .../public/utils/format_stat_value.ts | 26 ++++-------- 9 files changed, 60 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index d6dccc6c79270..a5560b8b25cbf 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -20,20 +20,12 @@ const App = () => { {Object.keys(routes).map((key) => { const path = key as keyof typeof routes; const route = routes[path]; - return ( - { - const { query, path: pathParams } = useUrlParams(route); - return route.handler({ query, path: pathParams }); - }} - /> - ); + const Wrapper = () => { + const { query, path: pathParams } = useUrlParams(route); + return route.handler({ query, path: pathParams }); + }; + return ; })} - {/* - */} ); diff --git a/x-pack/plugins/observability/public/mock/apm.mock.ts b/x-pack/plugins/observability/public/mock/apm.mock.ts index 0d039fed01745..1f172776f9130 100644 --- a/x-pack/plugins/observability/public/mock/apm.mock.ts +++ b/x-pack/plugins/observability/public/mock/apm.mock.ts @@ -18,8 +18,8 @@ const response: ApmFetchDataResponse = { }), appLink: '/app/apm', stats: { - services: { label: 'Services', value: 11 }, - transactions: { label: 'Transactions', value: 312000, color: 'euiColorVis1' }, + services: { label: 'Services', value: 11, type: 'number' }, + transactions: { label: 'Transactions', value: 312000, type: 'number', color: 'euiColorVis1' }, }, series: { transactions: { diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index 2ce09b42267ed..40636e9dfd200 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -18,9 +18,9 @@ const response: LogsFetchDataResponse = { }), appLink: '/app/logs', stats: { - unknown: { label: 'Unknown', value: 73777 }, - 'kibana.log': { label: 'Kibana.log', value: 1018 }, - 'nginx.access': { label: 'Nginx.access', value: 5528 }, + unknown: { label: 'Unknown', value: 73777, type: 'number' }, + 'kibana.log': { label: 'Kibana.log', value: 1018, type: 'number' }, + 'nginx.access': { label: 'Nginx.access', value: 5528, type: 'number' }, }, series: { unknown: { diff --git a/x-pack/plugins/observability/public/mock/metrics.mock.ts b/x-pack/plugins/observability/public/mock/metrics.mock.ts index 8d3d1973cdb45..b3649da3a3351 100644 --- a/x-pack/plugins/observability/public/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/mock/metrics.mock.ts @@ -18,12 +18,12 @@ const response: MetricsFetchDataResponse = { }), appLink: '/app/apm', stats: { - hosts: { label: 'Hosts', value: 11 }, - cpu: { label: 'CPU usage', pct: 80 }, - memory: { label: 'Memory Usage', pct: 36.2 }, - disk: { label: 'Disk Usage', pct: 32.4 }, - inboundTraffic: { label: 'Inbount traffic', bytes: 1024 }, - outboundTraffic: { label: 'Outbount traffic', bytes: 1024 }, + hosts: { label: 'Hosts', value: 11, type: 'number' }, + cpu: { label: 'CPU usage', value: 0.8, type: 'percent' }, + memory: { label: 'Memory Usage', value: 0.362, type: 'percent' }, + disk: { label: 'Disk Usage', value: 0.324, type: 'percent' }, + inboundTraffic: { label: 'Inbount traffic', value: 1024, type: 'bytesPerSecond' }, + outboundTraffic: { label: 'Outbount traffic', value: 1024, type: 'bytesPerSecond' }, }, series: { outboundTraffic: { diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts index c990ba8da4d70..2a3b0e081d303 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -14,9 +14,9 @@ const response: UptimeFetchDataResponse = { title: 'Uptime', appLink: '/app/uptime', stats: { - monitors: { label: 'Monitors', value: 5 }, - down: { label: 'Down', value: 115 }, - up: { label: 'Up', value: 582 }, + monitors: { label: 'Monitors', value: 5, type: 'number' }, + down: { label: 'Down', value: 115, type: 'number' }, + up: { label: 'Up', value: 582, type: 'number' }, }, series: { down: { diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 0316d215a8661..f77fa1660cbef 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -46,8 +46,6 @@ export const Home = () => { ]); }, [core]); - const apps = appsSection.filter((app) => app.id !== 'alert'); - return ( { - {apps.map((app) => ( + {appsSection.map((app) => ( = DecodeParams; @@ -27,8 +26,7 @@ export const routes = { params: {}, }, '/overview': { - handler: ({ query }: { query: any }) => { - console.log('### caue: query', query); + handler: ({ query }: any) => { return ; }, params: { @@ -38,16 +36,4 @@ export const routes = { }), }, }, - '/overview/:id': { - handler: ({ query, path }: { query: any; path: any }) => { - return ; - }, - params: { - path: t.type({ id: jsonRt.pipe(t.number) }), - query: t.partial({ - rangeFrom: t.string, - rangeTo: t.string, - }), - }, - }, }; diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.test.ts b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts index 3bb44a2756336..aa9849bc74ef3 100644 --- a/x-pack/plugins/observability/public/utils/format_stat_value.test.ts +++ b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts @@ -4,27 +4,46 @@ * you may not use this file except in compliance with the Elastic License. */ import { formatStatValue } from './format_stat_value'; +import { Stat } from '../typings/fetch_data_response'; describe('formatStatValue', () => { - it('formats value as numeral', () => { + it('formats value as number', () => { const stat = { + type: 'number', label: 'numeral stat', value: 1000, - }; + } as Stat; expect(formatStatValue(stat)).toEqual('1k'); }); it('formats value as bytes', () => { - const stat = { - label: 'bytes stat', - bytes: 11.4, - }; - expect(formatStatValue(stat)).toEqual('11.4 Mb/s'); + expect( + formatStatValue({ + type: 'bytesPerSecond', + label: 'bytes stat', + value: 1, + } as Stat) + ).toEqual('1.0B/s'); + expect( + formatStatValue({ + type: 'bytesPerSecond', + label: 'bytes stat', + value: 1048576, + } as Stat) + ).toEqual('1.0MB/s'); + expect( + formatStatValue({ + type: 'bytesPerSecond', + label: 'bytes stat', + value: 1073741824, + } as Stat) + ).toEqual('1.0GB/s'); }); - it('formats value as percentage', () => { + it('formats value as percent', () => { const stat = { - label: 'bytes stat', - pct: 0.841, - }; + type: 'percent', + label: 'percent stat', + value: 0.841, + } as Stat; expect(formatStatValue(stat)).toEqual('84.1%'); }); }); diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.ts b/x-pack/plugins/observability/public/utils/format_stat_value.ts index 81c7ca62c064f..547409566f705 100644 --- a/x-pack/plugins/observability/public/utils/format_stat_value.ts +++ b/x-pack/plugins/observability/public/utils/format_stat_value.ts @@ -5,24 +5,16 @@ */ import numeral from '@elastic/numeral'; -import { Numeral, Percentage, Bytes } from '../typings/fetch_data_response'; - -type Stat = Numeral | Percentage | Bytes; - -function isBytes(stat: Stat): stat is Bytes { - return (stat as Bytes).bytes !== undefined; -} - -function isPercentage(stat: Stat): stat is Percentage { - return (stat as Percentage).pct !== undefined; -} +import { Stat } from '../typings/fetch_data_response'; export function formatStatValue(stat: Stat) { - if (isBytes(stat)) { - return `${stat.bytes} Mb/s`; - } else if (isPercentage(stat)) { - return numeral(stat.pct).format('0.0%'); - } else { - return numeral(stat.value).format('0a'); + const { value, type } = stat; + switch (type) { + case 'bytesPerSecond': + return `${numeral(value).format('0.0b')}/s`; + case 'number': + return numeral(value).format('0a'); + case 'percent': + return numeral(value).format('0.0%'); } } From a75c3b7458d333db61521d4f19d8424b3fef1afb Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 25 Jun 2020 14:44:57 +0200 Subject: [PATCH 47/97] adding bucket size --- .../components/shared/data_picker/index.tsx | 2 +- .../public/pages/overview/index.tsx | 11 ++- .../utils/get_bucket_size/calculate_auto.js | 77 +++++++++++++++++++ .../utils/get_bucket_size/index.test.ts | 70 +++++++++++++++++ .../public/utils/get_bucket_size/index.ts | 36 +++++++++ .../utils/get_bucket_size/unit_to_seconds.ts | 20 +++++ 6 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js create mode 100644 x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts create mode 100644 x-pack/plugins/observability/public/utils/get_bucket_size/index.ts create mode 100644 x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts diff --git a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx index 050fd522e25eb..eeecd405e505b 100644 --- a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx @@ -86,7 +86,7 @@ export const DatePicker = ({ rangeFrom, rangeTo }: Props) => { // TODO: maybe use Generics to specify what this component expects const { - refreshPaused = true, + refreshPaused = timePickerRefreshInterval.pause, refreshInterval = timePickerRefreshInterval.value, } = useQueryParams(); diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index b0e2288ad902d..1dbf9e76342ef 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -7,6 +7,7 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui' import { isEmpty } from 'lodash'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; +import moment from 'moment'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { APMSection } from '../../components/app/section/apm'; @@ -20,6 +21,7 @@ import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_sett import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { appsSection } from '../home/section'; +import { getBucketSize } from '../../utils/get_bucket_size'; interface Props { routeParams: RouteParams<'/overview'>; @@ -33,9 +35,14 @@ export const Overview = ({ routeParams }: Props) => { const { data = [] } = useFetcher(() => { const startTime = getParsedDate(rangeFrom); - const endTime = getParsedDate(rangeTo); + const endTime = getParsedDate(rangeTo, { roundUp: true }); if (startTime && endTime) { - const params = { startTime, endTime, bucketSize: '3' }; + const { intervalString } = getBucketSize({ + start: moment.utc(startTime).valueOf(), + end: moment.utc(endTime).valueOf(), + minInterval: 'auto', + }); + const params = { startTime, endTime, bucketSize: intervalString }; const apmDataPromise = getDataHandler('apm')?.fetchData(params); const logsDataPromise = getDataHandler('infra_logs')?.fetchData(params); const metricsDataPromise = getDataHandler('infra_metrics')?.fetchData(params); diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js b/x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js new file mode 100644 index 0000000000000..1608003641596 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +const d = moment.duration; + +const roundingRules = [ + [d(500, 'ms'), d(100, 'ms')], + [d(5, 'second'), d(1, 'second')], + [d(7.5, 'second'), d(5, 'second')], + [d(15, 'second'), d(10, 'second')], + [d(45, 'second'), d(30, 'second')], + [d(3, 'minute'), d(1, 'minute')], + [d(9, 'minute'), d(5, 'minute')], + [d(20, 'minute'), d(10, 'minute')], + [d(45, 'minute'), d(30, 'minute')], + [d(2, 'hour'), d(1, 'hour')], + [d(6, 'hour'), d(3, 'hour')], + [d(24, 'hour'), d(12, 'hour')], + [d(1, 'week'), d(1, 'd')], + [d(3, 'week'), d(1, 'week')], + [d(1, 'year'), d(1, 'month')], + [Infinity, d(1, 'year')], +]; + +const revRoundingRules = roundingRules.slice(0).reverse(); + +function find(rules, check, last) { + function pick(buckets, duration) { + const target = duration / buckets; + let lastResp = null; + + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + const resp = check(rule[0], rule[1], target); + + if (resp == null) { + if (!last) continue; + if (lastResp) return lastResp; + break; + } + + if (!last) return resp; + lastResp = resp; + } + + // fallback to just a number of milliseconds, ensure ms is >= 1 + const ms = Math.max(Math.floor(target), 1); + return moment.duration(ms, 'ms'); + } + + return (buckets, duration) => { + const interval = pick(buckets, duration); + if (interval) return moment.duration(interval._data); + }; +} + +export const calculateAuto = { + near: find( + revRoundingRules, + function near(bound, interval, target) { + if (bound > target) return interval; + }, + true + ), + + lessThan: find(revRoundingRules, function lessThan(_bound, interval, target) { + if (interval < target) return interval; + }), + + atLeast: find(revRoundingRules, function atLeast(_bound, interval, target) { + if (interval <= target) return interval; + }), +}; diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts new file mode 100644 index 0000000000000..39c4aedaa6013 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getBucketSize } from './index'; +import moment from 'moment'; + +describe('getBuckets', () => { + describe("minInterval 'auto'", () => { + it('last 15 minutes', () => { + const start = moment().subtract(15, 'minutes').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 10, + intervalString: '10s', + }); + }); + it('last 1 hour', () => { + const start = moment().subtract(1, 'hour').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 30, + intervalString: '30s', + }); + }); + it('last 1 week', () => { + const start = moment().subtract(1, 'week').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 3600, + intervalString: '3600s', + }); + }); + it('last 30 days', () => { + const start = moment().subtract(30, 'days').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 43200, + intervalString: '43200s', + }); + }); + it('last 1 year', () => { + const start = moment().subtract(1, 'year').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 86400, + intervalString: '86400s', + }); + }); + }); + describe("minInterval '30s'", () => { + it('last 15 minutes', () => { + const start = moment().subtract(15, 'minutes').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: '30s' })).toEqual({ + bucketSize: 30, + intervalString: '30s', + }); + }); + it('last 1 year', () => { + const start = moment().subtract(1, 'year').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: '30s' })).toEqual({ + bucketSize: 86400, + intervalString: '86400s', + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts new file mode 100644 index 0000000000000..e19f463c76656 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +// @ts-ignore +import { calculateAuto } from './calculate_auto'; +// @ts-ignore +import { unitToSeconds } from './unit_to_seconds'; + +export function getBucketSize({ + start, + end, + minInterval, +}: { + start: number; + end: number; + minInterval: string; +}) { + const duration = moment.duration(end - start, 'ms'); + const bucketSize = Math.max(calculateAuto.near(100, duration).asSeconds(), 1); + const intervalString = `${bucketSize}s`; + const matches = minInterval && minInterval.match(/^([\d]+)([shmdwMy]|ms)$/); + const minBucketSize = matches ? Number(matches[1]) * unitToSeconds(matches[2]) : 0; + + if (bucketSize < minBucketSize) { + return { + bucketSize: minBucketSize, + intervalString: minInterval, + }; + } + + return { bucketSize, intervalString }; +} diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts new file mode 100644 index 0000000000000..33f7a128cb5c8 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const units = { + ms: 0.001, + s: 1, + m: 60, + h: 3600, + d: 86400, + w: 86400 * 7, // Hum... might be wrong + M: 86400 * 30, // this too... 29,30,31? + y: 86400 * 356, // Leap year? +}; + +export function unitToSeconds(unit: string) { + return units[unit as keyof typeof units]; +} From 78463fa29f66d36cd101b54b051889956226eae8 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 26 Jun 2020 11:15:54 +0200 Subject: [PATCH 48/97] adding group property on logs --- .../public/components/app/section/logs/index.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 5b4523c4da9d4..c0a6730ff97b6 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -77,9 +77,14 @@ export const LogsSection = ({ data }: Props) => { legendPosition="bottom" xDomain={{ min, max }} /> - {Object.values(data.series).map((serie) => { + {Object.keys(data.series).map((key) => { + const serie = data.series[key]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); return ( - + { xAccessor={'x'} yAccessors={['y']} stackAccessors={['x']} - data={serie.coordinates} + splitSeriesAccessors={['g']} + data={chartData} /> Date: Fri, 26 Jun 2020 16:13:59 +0200 Subject: [PATCH 49/97] adding home page --- .../components/app/section/apm/index.tsx | 26 +-- .../public/components/app/section/index.tsx | 15 +- .../components/app/section/logs/index.tsx | 13 +- .../components/app/section/metrics/index.tsx | 17 +- .../components/app/section/uptime/index.tsx | 49 +++--- .../observability/public/pages/home/index.tsx | 150 ++++-------------- .../public/pages/overview/index.tsx | 35 ++-- .../public/pages/start/index.tsx | 131 +++++++++++++++ x-pack/plugins/observability/public/plugin.ts | 12 +- .../observability/public/routes/index.tsx | 9 +- 10 files changed, 276 insertions(+), 181 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/start/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 14bb638f7c796..1b62857d10958 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -12,12 +12,15 @@ import { LIGHT_THEME, niceTimeFormatter, Settings, + ScaleType, + Position, } from '@elastic/charts'; import d3 from 'd3'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; +import { i18n } from '@kbn/i18n'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; @@ -44,18 +47,24 @@ export const APMSection = ({ data }: Props) => { const getSerieColor = (color?: string) => { if (color) { - return theme.eui[color]; + return color; } }; return ( - + {Object.keys(data.stats).map((key) => { const stat = data.stats[key as keyof ApmFetchDataResponse['stats']]; return ( - + ); })} @@ -76,22 +85,21 @@ export const APMSection = ({ data }: Props) => { id="transactions" name="Transactions" data={transactionSeries.coordinates} - xScaleType="time" + xScaleType={ScaleType.Time} + yScaleType={ScaleType.Linear} xAccessor={'x'} yAccessors={['y']} color={getSerieColor(transactionSeries.color)} - groupId="transactions" /> numeral(d).format('0a')} /> + )} - ); diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 1a321e51b2278..0d7cb44451805 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -6,21 +6,24 @@ import { EuiAccordion, EuiButtonEmpty, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { EuiText } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; interface Props { title: string; + subtitle: string; appLink?: string; children: React.ReactNode; } -export const SectionContainer = ({ title, appLink, children }: Props) => { +export const SectionContainer = ({ title, appLink, children, subtitle }: Props) => { return ( +
{title}
} @@ -32,7 +35,13 @@ export const SectionContainer = ({ title, appLink, children }: Props) => { } > - {children} + + +

{subtitle}

+
+ + {children} +
); }; diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index c0a6730ff97b6..10fed38e6a475 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -23,6 +23,7 @@ import numeral from '@elastic/numeral'; import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiStat } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; @@ -56,13 +57,19 @@ export const LogsSection = ({ data }: Props) => { }; return ( - + {Object.keys(data.stats).map((key) => { const stat = data.stats[key as keyof LogsFetchDataResponse['stats']]; return ( - + ); })} @@ -86,7 +93,7 @@ export const LogsSection = ({ data }: Props) => { return ( { } return ( - + {Object.keys(data.stats).map((key) => { const statKey = key as keyof MetricsFetchDataResponse['stats']; @@ -33,12 +41,15 @@ export const MetricsSection = ({ data }: Props) => { const chart = serie ? ( ) : ( - + <> + + + ); return ( - + {statKey !== 'hosts' && chart} diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index c754e38a800e6..b96f99e5c9780 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -4,27 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useContext } from 'react'; -import d3 from 'd3'; import { - Chart, - Settings, - BarSeries, Axis, - Position, + BarSeries, + Chart, DARK_THEME, LIGHT_THEME, - ScaleType, niceTimeFormatter, + Position, + ScaleType, + Settings, } from '@elastic/charts'; -import { ThemeContext } from 'styled-components'; +import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { EuiFlexGroup } from '@elastic/eui'; -import { EuiFlexItem } from '@elastic/eui'; -import { EuiStat } from '@elastic/eui'; -import { formatStatValue } from '../../../../utils/format_stat_value'; +import d3 from 'd3'; +import React, { Fragment, useContext } from 'react'; +import { ThemeContext } from 'styled-components'; +import { i18n } from '@kbn/i18n'; import { SectionContainer } from '../'; import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { formatStatValue } from '../../../../utils/format_stat_value'; interface Props { data?: UptimeFetchDataResponse; @@ -53,13 +52,19 @@ export const UptimeSection = ({ data }: Props) => { }; return ( - + {Object.keys(data.stats).map((key) => { const stat = data.stats[key as keyof UptimeFetchDataResponse['stats']]; return ( - + ); })} @@ -74,18 +79,24 @@ export const UptimeSection = ({ data }: Props) => { legendPosition="bottom" xDomain={{ min, max }} /> - {Object.values(data.series).map((serie) => { + {Object.keys(data.series).map((key) => { + const serie = data.series[key as keyof UptimeFetchDataResponse['series']]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); return ( - + { id="y-axis" showGridLines position={Position.Left} - tickFormat={(d: any) => numeral(d).format('0a')} + tickFormat={(x: any) => numeral(x).format('0a')} /> ); diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index f77fa1660cbef..c7d42b645af72 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -3,129 +3,39 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { - EuiButton, - EuiCard, - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiImage, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { useEffect, useContext } from 'react'; -import styled, { ThemeContext } from 'styled-components'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { appsSection } from './section'; -import { WithHeaderLayout } from '../../components/app/layout/with_header'; - -const EuiCardWithoutPadding = styled(EuiCard)` - padding: 0; -`; +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { ObservabilityApp } from '../../../typings/common'; +import { getDataHandler } from '../../data_handler'; +import { useFetcher } from '../../hooks/use_fetcher'; + +const fetchData = async (): Promise> => { + const apmPromise = getDataHandler('apm')?.hasData(); + const uptimePromise = getDataHandler('uptime')?.hasData(); + const logsPromise = getDataHandler('infra_logs')?.hasData(); + const metricsPromise = getDataHandler('infra_metrics')?.hasData(); + const [apm, uptime, logs, metrics] = await Promise.all([ + apmPromise, + uptimePromise, + logsPromise, + metricsPromise, + ]); + return { apm, uptime, infra_logs: logs, infra_metrics: metrics }; +}; export const Home = () => { - const { core } = usePluginContext(); - const theme = useContext(ThemeContext); - - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('xpack.observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, [core]); - - return ( - - - {/* title and description */} - - -

- {i18n.translate('xpack.observability.home.sectionTitle', { - defaultMessage: 'Unified visibility across your entire ecosystem', - })} -

-
- - - {i18n.translate('xpack.observability.home.sectionsubtitle', { - defaultMessage: - 'Monitor, analyze, and react to events happening anywhere in your environment by bringing logs, metrics, and traces together at scale in a single stack.', - })} - -
+ const history = useHistory(); + const { data = {} } = useFetcher(fetchData, []); - {/* Apps sections */} - - - - - - {appsSection.map((app) => ( - - } - title={ - -

{app.title}

-
- } - description={app.description} - /> -
- ))} -
-
- - - -
-
+ const values = Object.values(data); + const hasSomeData = values.length ? values.some((hasData) => hasData) : null; - + if (hasSomeData === true) { + history.push({ pathname: '/overview', state: { hasData: data } }); + } + if (hasSomeData === false) { + history.push({ pathname: '/start' }); + } - {/* Get started button */} - - - - - {i18n.translate('xpack.observability.home.getStatedButton', { - defaultMessage: 'Get started', - })} - - - - -
-
- ); + return <>; }; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 1dbf9e76342ef..6473d99c4455b 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { isEmpty } from 'lodash'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import moment from 'moment'; +import { useLocation } from 'react-router-dom'; +import { ObservabilityApp } from '../../../typings/common'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { APMSection } from '../../components/app/section/apm'; @@ -27,7 +28,13 @@ interface Props { routeParams: RouteParams<'/overview'>; } +interface LocationState { + hasData: Record; +} + export const Overview = ({ routeParams }: Props) => { + const { state } = useLocation(); + const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); @@ -43,31 +50,21 @@ export const Overview = ({ routeParams }: Props) => { minInterval: 'auto', }); const params = { startTime, endTime, bucketSize: intervalString }; - const apmDataPromise = getDataHandler('apm')?.fetchData(params); - const logsDataPromise = getDataHandler('infra_logs')?.fetchData(params); - const metricsDataPromise = getDataHandler('infra_metrics')?.fetchData(params); - const uptimeDataPromise = getDataHandler('uptime')?.fetchData(params); + const apmDataPromise = state.hasData.apm && getDataHandler('apm')?.fetchData(params); + const logsDataPromise = + state.hasData.infra_logs && getDataHandler('infra_logs')?.fetchData(params); + const metricsDataPromise = + state.hasData.infra_metrics && getDataHandler('infra_metrics')?.fetchData(params); + const uptimeDataPromise = state.hasData.uptime && getDataHandler('uptime')?.fetchData(params); + // TODO: caue fix it return Promise.all([apmDataPromise, logsDataPromise, metricsDataPromise, uptimeDataPromise]); } }, [rangeFrom, rangeTo]); const [apmData, logsData, metricsData, uptimeData] = data; - const emptySections = appsSection.filter((app) => { - switch (app.id) { - case 'apm': - return isEmpty(apmData); - case 'infra_logs': - return isEmpty(logsData); - case 'infra_metrics': - return isEmpty(metricsData); - case 'uptime': - return isEmpty(uptimeData); - default: - return true; - } - }); + const emptySections = appsSection.filter(({ id }) => !state.hasData[id]); return ( { + const { core } = usePluginContext(); + const theme = useContext(ThemeContext); + + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('xpack.observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, [core]); + + return ( + + + {/* title and description */} + + +

+ {i18n.translate('xpack.observability.home.sectionTitle', { + defaultMessage: 'Unified visibility across your entire ecosystem', + })} +

+
+ + + {i18n.translate('xpack.observability.home.sectionsubtitle', { + defaultMessage: + 'Monitor, analyze, and react to events happening anywhere in your environment by bringing logs, metrics, and traces together at scale in a single stack.', + })} + +
+ + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( + + } + title={ + +

{app.title}

+
+ } + description={app.description} + /> +
+ ))} +
+
+ + + +
+
+ + + + {/* Get started button */} + + + + + {i18n.translate('xpack.observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index be22af87311e9..f60453da95307 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -12,7 +12,6 @@ import { } from '../../../../src/core/public'; import { registerDataHandler } from './data_handler'; // TODO: caue: remove it later -import { fetchApmData } from './mock/apm.mock'; import { fetchLogsData } from './mock/logs.mock'; import { fetchMetricsData } from './mock/metrics.mock'; import { fetchUptimeData } from './mock/uptime.mock'; @@ -45,17 +44,22 @@ export class Plugin implements PluginClass { registerDataHandler({ appName: 'infra_logs', fetchData: fetchLogsData, - hasData: () => Promise.resolve(true), + hasData: () => + new Promise((resolve) => { + setTimeout(() => { + resolve(false); + }, 5000); + }), }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(true), + hasData: () => Promise.resolve(false), }); registerDataHandler({ appName: 'uptime', fetchData: fetchUptimeData, - hasData: () => Promise.resolve(true), + hasData: () => Promise.resolve(false), }); return { diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index cdda2cb4b8360..e2df6631340ee 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -5,8 +5,9 @@ */ import React from 'react'; import * as t from 'io-ts'; -import { Home } from '../pages/home'; +import { Start } from '../pages/start'; import { Overview } from '../pages/overview'; +import { Home } from '../pages/home'; export type RouteParams = DecodeParams; @@ -25,6 +26,12 @@ export const routes = { }, params: {}, }, + '/start': { + handler: () => { + return ; + }, + params: {}, + }, '/overview': { handler: ({ query }: any) => { return ; From 3c79436d6f5bc4657c1463e18036168b48387f2c Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 26 Jun 2020 16:18:12 +0200 Subject: [PATCH 50/97] dont break page if location state is undefined --- .../observability/public/pages/overview/index.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 6473d99c4455b..e86c75ed327a3 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -50,21 +50,18 @@ export const Overview = ({ routeParams }: Props) => { minInterval: 'auto', }); const params = { startTime, endTime, bucketSize: intervalString }; - const apmDataPromise = state.hasData.apm && getDataHandler('apm')?.fetchData(params); - const logsDataPromise = - state.hasData.infra_logs && getDataHandler('infra_logs')?.fetchData(params); - const metricsDataPromise = - state.hasData.infra_metrics && getDataHandler('infra_metrics')?.fetchData(params); - const uptimeDataPromise = state.hasData.uptime && getDataHandler('uptime')?.fetchData(params); + const apmDataPromise = getDataHandler('apm')?.fetchData(params); + const logsDataPromise = getDataHandler('infra_logs')?.fetchData(params); + const metricsDataPromise = getDataHandler('infra_metrics')?.fetchData(params); + const uptimeDataPromise = getDataHandler('uptime')?.fetchData(params); - // TODO: caue fix it return Promise.all([apmDataPromise, logsDataPromise, metricsDataPromise, uptimeDataPromise]); } }, [rangeFrom, rangeTo]); const [apmData, logsData, metricsData, uptimeData] = data; - const emptySections = appsSection.filter(({ id }) => !state.hasData[id]); + const emptySections = appsSection.filter(({ id }) => state && !state.hasData[id]); return ( Date: Mon, 29 Jun 2020 11:13:52 +0200 Subject: [PATCH 51/97] each component fetches its own data --- .../components/app/section/apm/index.tsx | 48 ++++--- .../public/components/app/section/index.tsx | 12 +- .../components/app/section/logs/index.tsx | 120 ++++++++++-------- .../components/app/section/metrics/index.tsx | 63 +++++---- .../components/app/section/uptime/index.tsx | 120 ++++++++++-------- .../observability/public/mock/logs.mock.ts | 6 +- .../observability/public/mock/metrics.mock.ts | 6 +- .../observability/public/mock/uptime.mock.ts | 6 +- .../public/pages/overview/index.tsx | 62 ++++----- 9 files changed, 245 insertions(+), 198 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 1b62857d10958..1eb577e6b0521 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -21,24 +21,31 @@ import numeral from '@elastic/numeral'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; +import { getDataHandler } from '../../../../data_handler'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; interface Props { - data?: ApmFetchDataResponse; + startTime?: string; + endTime?: string; + bucketSize?: string; } -export const APMSection = ({ data }: Props) => { +export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); + const { data } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('apm')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); - if (!data) { - return null; - } - - const transactionSeries = data.series.transactions; + const transactionSeries = data?.series.transactions; - const xCoordinates = transactionSeries.coordinates.map((coordinate) => coordinate.x); + const xCoordinates = transactionSeries + ? transactionSeries.coordinates.map((coordinate) => coordinate.x) + : [0]; const min = d3.min(xCoordinates); const max = d3.max(xCoordinates); @@ -53,27 +60,26 @@ export const APMSection = ({ data }: Props) => { return ( - {Object.keys(data.stats).map((key) => { - const stat = data.stats[key as keyof ApmFetchDataResponse['stats']]; - return ( - - - - ); - })} + {data && + Object.keys(data.stats).map((key) => { + const stat = data?.stats[key as keyof ApmFetchDataResponse['stats']]; + return ( + + + + ); + })} { - console.log('#### APM', x); - }} + onBrushEnd={({ x }) => {}} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} showLegend={true} legendPosition="bottom" diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 0d7cb44451805..9d55008b77d0d 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -28,11 +28,13 @@ export const SectionContainer = ({ title, appLink, children, subtitle }: Props) } extraAction={ - - {i18n.translate('xpack.observability.chart.viewInAppLabel', { - defaultMessage: 'View in app', - })} - + appLink && ( + + {i18n.translate('xpack.observability.chart.viewInAppLabel', { + defaultMessage: 'View in app', + })} + + ) } > diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 10fed38e6a475..ceb5d4edf3caf 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -24,24 +24,32 @@ import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiStat } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { getDataHandler } from '../../../../data_handler'; interface Props { - data?: LogsFetchDataResponse; + startTime?: string; + endTime?: string; + bucketSize?: string; } -export const LogsSection = ({ data }: Props) => { +export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); - if (!data) { - return null; - } + const { data } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); - const xCoordinates = Object.values(data.series).flatMap((serie) => - serie.coordinates.map((coordinate) => coordinate.x) - ); + const xCoordinates = data + ? Object.values(data.series).flatMap((serie) => + serie.coordinates.map((coordinate) => coordinate.x) + ) + : [0]; const min = d3.min(xCoordinates); const max = d3.max(xCoordinates); @@ -51,73 +59,73 @@ export const LogsSection = ({ data }: Props) => { const customColors = { colors: { vizColors: euiPaletteColorBlind({ - rotations: Math.ceil(Object.keys(data.series).length / 10), + rotations: data ? Math.ceil(Object.keys(data.series).length / 10) : 1, }), }, }; return ( - {Object.keys(data.stats).map((key) => { - const stat = data.stats[key as keyof LogsFetchDataResponse['stats']]; - return ( - - - - ); - })} + {data && + Object.keys(data.stats).map((key) => { + const stat = data.stats[key as keyof LogsFetchDataResponse['stats']]; + return ( + + + + ); + })} { - console.log('#### Logs', x); - }} + onBrushEnd={({ x }) => {}} theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} showLegend legendPosition="bottom" xDomain={{ min, max }} /> - {Object.keys(data.series).map((key) => { - const serie = data.series[key]; - const chartData = serie.coordinates.map((coordinate) => ({ - ...coordinate, - g: serie.label, - })); - return ( - - - - numeral(d).format('0a')} - /> - - ); - })} + {data && + Object.keys(data.series).map((key) => { + const serie = data.series[key]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); + return ( + + + + numeral(d).format('0a')} + /> + + ); + })} ); diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index d495fe8ded4c3..75e314a332558 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -9,52 +9,59 @@ import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { SectionContainer } from '../'; import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; +import { getDataHandler } from '../../../../data_handler'; interface Props { - data?: MetricsFetchDataResponse; + startTime?: string; + endTime?: string; + bucketSize?: string; } -export const MetricsSection = ({ data }: Props) => { - if (!data) { - return null; - } +export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { + const { data } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('infra_metrics')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); return ( - {Object.keys(data.stats).map((key) => { - const statKey = key as keyof MetricsFetchDataResponse['stats']; - const stat = data.stats[statKey]; - const value = formatStatValue(stat); + {data && + Object.keys(data.stats).map((key) => { + const statKey = key as keyof MetricsFetchDataResponse['stats']; + const stat = data.stats[statKey]; + const value = formatStatValue(stat); - const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; + const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; - const chart = serie ? ( - - ) : ( - <> - - - - ); + const chart = serie ? ( + + ) : ( + <> + + + + ); - return ( - - - {statKey !== 'hosts' && chart} - - - ); - })} + return ( + + + {statKey !== 'hosts' && chart} + + + ); + })} ); diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index b96f99e5c9780..58cdd2f81320c 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -21,24 +21,32 @@ import d3 from 'd3'; import React, { Fragment, useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { SectionContainer } from '../'; import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; +import { getDataHandler } from '../../../../data_handler'; interface Props { - data?: UptimeFetchDataResponse; + startTime?: string; + endTime?: string; + bucketSize?: string; } -export const UptimeSection = ({ data }: Props) => { +export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); - if (!data) { - return null; - } + const { data } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); - const xCoordinates = Object.values(data.series).flatMap((serie) => - serie.coordinates.map((coordinate) => coordinate.x) - ); + const xCoordinates = data + ? Object.values(data.series).flatMap((serie) => + serie.coordinates.map((coordinate) => coordinate.x) + ) + : [0]; const min = d3.min(xCoordinates); const max = d3.max(xCoordinates); @@ -53,67 +61,67 @@ export const UptimeSection = ({ data }: Props) => { return ( - {Object.keys(data.stats).map((key) => { - const stat = data.stats[key as keyof UptimeFetchDataResponse['stats']]; - return ( - - - - ); - })} + {data && + Object.keys(data.stats).map((key) => { + const stat = data.stats[key as keyof UptimeFetchDataResponse['stats']]; + return ( + + + + ); + })} { - console.log('#### Logs', x); - }} + onBrushEnd={({ x }) => {}} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} showLegend legendPosition="bottom" xDomain={{ min, max }} /> - {Object.keys(data.series).map((key) => { - const serie = data.series[key as keyof UptimeFetchDataResponse['series']]; - const chartData = serie.coordinates.map((coordinate) => ({ - ...coordinate, - g: serie.label, - })); - return ( - - - - numeral(x).format('0a')} - /> - - ); - })} + {data && + Object.keys(data.series).map((key) => { + const serie = data.series[key as keyof UptimeFetchDataResponse['series']]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); + return ( + + + + numeral(x).format('0a')} + /> + + ); + })} ); diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index 40636e9dfd200..8220066d678c7 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -9,7 +9,11 @@ import { LogsFetchDataResponse } from '../typings/fetch_data_response'; import { FetchData } from '../data_handler'; export const fetchLogsData: FetchData = () => { - return Promise.resolve(response); + return new Promise((resolve) => { + setTimeout(() => { + resolve(response); + }, 2000); + }); }; const response: LogsFetchDataResponse = { diff --git a/x-pack/plugins/observability/public/mock/metrics.mock.ts b/x-pack/plugins/observability/public/mock/metrics.mock.ts index b3649da3a3351..8cd1a463330e5 100644 --- a/x-pack/plugins/observability/public/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/mock/metrics.mock.ts @@ -9,7 +9,11 @@ import { MetricsFetchDataResponse } from '../typings/fetch_data_response'; import { FetchData } from '../data_handler'; export const fetchMetricsData: FetchData = () => { - return Promise.resolve(response); + return new Promise((resolve) => { + setTimeout(() => { + resolve(response); + }, 1500); + }); }; const response: MetricsFetchDataResponse = { diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts index 2a3b0e081d303..22077691898d1 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -7,7 +7,11 @@ import { UptimeFetchDataResponse } from '../typings/fetch_data_response'; import { FetchData } from '../data_handler'; export const fetchUptimeData: FetchData = () => { - return Promise.resolve(response); + return new Promise((resolve) => { + setTimeout(() => { + resolve(response); + }, 3000); + }); }; const response: UptimeFetchDataResponse = { diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index e86c75ed327a3..b5343bc13ae19 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import React, { useContext } from 'react'; -import { ThemeContext } from 'styled-components'; import moment from 'moment'; +import React, { useContext } from 'react'; import { useLocation } from 'react-router-dom'; +import { ThemeContext } from 'styled-components'; import { ObservabilityApp } from '../../../typings/common'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; @@ -16,13 +16,11 @@ import { LogsSection } from '../../components/app/section/logs'; import { MetricsSection } from '../../components/app/section/metrics'; import { UptimeSection } from '../../components/app/section/uptime'; import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; -import { getDataHandler } from '../../data_handler'; -import { useFetcher } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; -import { appsSection } from '../home/section'; import { getBucketSize } from '../../utils/get_bucket_size'; +import { appsSection } from '../home/section'; interface Props { routeParams: RouteParams<'/overview'>; @@ -40,26 +38,16 @@ export const Overview = ({ routeParams }: Props) => { const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to } = routeParams.query; - const { data = [] } = useFetcher(() => { - const startTime = getParsedDate(rangeFrom); - const endTime = getParsedDate(rangeTo, { roundUp: true }); - if (startTime && endTime) { - const { intervalString } = getBucketSize({ - start: moment.utc(startTime).valueOf(), - end: moment.utc(endTime).valueOf(), - minInterval: 'auto', - }); - const params = { startTime, endTime, bucketSize: intervalString }; - const apmDataPromise = getDataHandler('apm')?.fetchData(params); - const logsDataPromise = getDataHandler('infra_logs')?.fetchData(params); - const metricsDataPromise = getDataHandler('infra_metrics')?.fetchData(params); - const uptimeDataPromise = getDataHandler('uptime')?.fetchData(params); - - return Promise.all([apmDataPromise, logsDataPromise, metricsDataPromise, uptimeDataPromise]); - } - }, [rangeFrom, rangeTo]); - - const [apmData, logsData, metricsData, uptimeData] = data; + const startTime = getParsedDate(rangeFrom); + const endTime = getParsedDate(rangeTo, { roundUp: true }); + const bucketSize = + startTime && endTime + ? getBucketSize({ + start: moment.utc(startTime).valueOf(), + end: moment.utc(endTime).valueOf(), + minInterval: 'auto', + }) + : undefined; const emptySections = appsSection.filter(({ id }) => state && !state.hasData[id]); @@ -77,16 +65,32 @@ export const Overview = ({ routeParams }: Props) => { - + - + - + - + From 04b3e721cdf818b7379f75a99cf218d5993499fa Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 29 Jun 2020 14:31:08 +0200 Subject: [PATCH 52/97] Refactoring --- .../components/app/empty_section/index.tsx | 9 +- .../components/app/layout/with_header.tsx | 1 + .../components/app/section/apm/index.tsx | 14 ++- .../public/components/app/section/index.tsx | 17 +-- .../components/app/section/logs/index.tsx | 14 ++- .../components/app/section/metrics/index.tsx | 12 +- .../components/app/section/uptime/index.tsx | 14 ++- .../observability/public/data_handler.ts | 7 ++ .../public/hooks/use_fetcher.tsx | 9 +- .../observability/public/mock/logs.mock.ts | 2 +- .../pages/{overview => dashboard}/index.tsx | 103 +++++++++--------- .../observability/public/pages/home/index.tsx | 25 +---- .../public/pages/{start => landing}/index.tsx | 2 +- x-pack/plugins/observability/public/plugin.ts | 4 +- .../observability/public/routes/index.tsx | 16 +-- 15 files changed, 141 insertions(+), 108 deletions(-) rename x-pack/plugins/observability/public/pages/{overview => dashboard}/index.tsx (60%) rename x-pack/plugins/observability/public/pages/{start => landing}/index.tsx (99%) diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx index 5a8775feb66f4..05747e945dbd5 100644 --- a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx @@ -8,7 +8,6 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { EuiButton } from '@elastic/eui'; import { ThemeContext } from 'styled-components'; import { EuiText } from '@elastic/eui'; -// TODO: caue fix it import { ISection } from '../../../pages/home/section'; interface Props { @@ -19,8 +18,12 @@ export const EmptySection = ({ section }: Props) => { const theme = useContext(ThemeContext); return ( {section.title}} titleSize="xs" diff --git a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx index bc6d321f5b4ab..af231a6f6accd 100644 --- a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx +++ b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx @@ -18,6 +18,7 @@ const Page = styled(EuiPage)` `; const Container = styled.div<{ color?: string }>` + overflow-y: hidden; min-height: calc(100vh - ${(props) => props.theme.eui.euiHeaderChildSize}); background: ${(props) => props.color}; `; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 1eb577e6b0521..a51bfe77ac92b 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -22,7 +22,7 @@ import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; import { getDataHandler } from '../../../../data_handler'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; @@ -35,7 +35,7 @@ interface Props { export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); - const { data } = useFetcher(() => { + const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { return getDataHandler('apm')?.fetchData({ startTime, endTime, bucketSize }); } @@ -60,6 +60,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { return ( { const stat = data?.stats[key as keyof ApmFetchDataResponse['stats']]; return ( - + ); })}
- + {}} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 9d55008b77d0d..b48d8836fb9fa 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -12,33 +12,36 @@ import { EuiSpacer } from '@elastic/eui'; interface Props { title: string; subtitle: string; + minHeight: number; appLink?: string; children: React.ReactNode; } -export const SectionContainer = ({ title, appLink, children, subtitle }: Props) => { +export const SectionContainer = ({ title, appLink, children, subtitle, minHeight }: Props) => { return ( +
{title}
} extraAction={ appLink && ( - {i18n.translate('xpack.observability.chart.viewInAppLabel', { - defaultMessage: 'View in app', - })} + + {i18n.translate('xpack.observability.chart.viewInAppLabel', { + defaultMessage: 'View in app', + })} + ) } > - - + +

{subtitle}

diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index ceb5d4edf3caf..7f4ff9f3d1553 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -24,7 +24,7 @@ import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiStat } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; @@ -39,7 +39,7 @@ interface Props { export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); - const { data } = useFetcher(() => { + const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize }); } @@ -66,6 +66,7 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { return ( { const stat = data.stats[key as keyof LogsFetchDataResponse['stats']]; return ( - + ); })}
- + {}} theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 75e314a332558..a03c254221549 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -9,7 +9,7 @@ import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { SectionContainer } from '../'; import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; @@ -22,7 +22,7 @@ interface Props { } export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { - const { data } = useFetcher(() => { + const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { return getDataHandler('infra_metrics')?.fetchData({ startTime, endTime, bucketSize }); } @@ -30,6 +30,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { return ( { return ( - + {statKey !== 'hosts' && chart} diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 58cdd2f81320c..62a7b5a159af0 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -21,7 +21,7 @@ import d3 from 'd3'; import React, { Fragment, useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { i18n } from '@kbn/i18n'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { SectionContainer } from '../'; import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; @@ -36,7 +36,7 @@ interface Props { export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); - const { data } = useFetcher(() => { + const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize }); } @@ -61,6 +61,7 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { return ( { const stat = data.stats[key as keyof UptimeFetchDataResponse['stats']]; return ( - + ); })}
- + {}} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} diff --git a/x-pack/plugins/observability/public/data_handler.ts b/x-pack/plugins/observability/public/data_handler.ts index 65f2c52a4e320..a03ea2ee1f50f 100644 --- a/x-pack/plugins/observability/public/data_handler.ts +++ b/x-pack/plugins/observability/public/data_handler.ts @@ -43,3 +43,10 @@ export function getDataHandler(appName: T) { return dataHandler as DataHandler; } } + +export async function fetchHasData() { + const apps: ObservabilityApp[] = ['apm', 'uptime', 'infra_logs', 'infra_metrics']; + const promises = apps.map((app) => getDataHandler(app)?.hasData()); + const [apm, uptime, logs, metrics] = await Promise.all(promises); + return { apm, uptime, infra_logs: logs, infra_metrics: metrics }; +} diff --git a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx index 3b7a08fa66b02..8ede2694ef964 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx @@ -27,8 +27,13 @@ type InferResponseType = Exclude extends Promise( fn: () => TReturn, - fnDeps: any[] + fnDeps: any[], + options: { + preservePreviousData?: boolean; + } = {} ): FetcherResult> & { refetch: () => void } { + const { preservePreviousData = true } = options; + const [result, setResult] = useState>>({ data: undefined, status: FETCH_STATUS.PENDING, @@ -42,7 +47,7 @@ export function useFetcher( } setResult((prevResult) => ({ - data: undefined, + data: preservePreviousData ? prevResult.data : undefined, status: FETCH_STATUS.LOADING, error: undefined, })); diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index 8220066d678c7..1386980d5d3e1 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -12,7 +12,7 @@ export const fetchLogsData: FetchData = () => { return new Promise((resolve) => { setTimeout(() => { resolve(response); - }, 2000); + }, 5000); }); }; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx similarity index 60% rename from x-pack/plugins/observability/public/pages/overview/index.tsx rename to x-pack/plugins/observability/public/pages/dashboard/index.tsx index b5343bc13ae19..d6de60c6a54a3 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -8,6 +8,7 @@ import moment from 'moment'; import React, { useContext } from 'react'; import { useLocation } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { ObservabilityApp } from '../../../typings/common'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; @@ -21,17 +22,16 @@ import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { appsSection } from '../home/section'; +import { useFetcher, FETCH_STATUS } from '../../hooks/use_fetcher'; +import { fetchHasData } from '../../data_handler'; interface Props { - routeParams: RouteParams<'/overview'>; + routeParams: RouteParams<'/dashboard'>; } -interface LocationState { - hasData: Record; -} - -export const Overview = ({ routeParams }: Props) => { - const { state } = useLocation(); +export const DashboardPage = ({ routeParams }: Props) => { + const result = useFetcher(() => fetchHasData(), []); + const hasData = result.data; const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); @@ -49,8 +49,6 @@ export const Overview = ({ routeParams }: Props) => { }) : undefined; - const emptySections = appsSection.filter(({ id }) => state && !state.hasData[id]); - return ( {
- - - - - - - - - - - - - - - - - - Alert chart goes here - + - - - {emptySections.map((app) => { + + {hasData?.infra_logs && ( + + + + )} + {hasData?.infra_metrics && ( + + + + )} + {hasData?.apm && ( + + + + )} + {hasData?.uptime && ( + + + + )} + + + + + + {appsSection + .filter(({ id }) => hasData && !hasData[id]) + .map((app) => { return ( ); })} - - +
); }; diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index c7d42b645af72..14d8bd57de41f 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -5,36 +5,21 @@ */ import React from 'react'; import { useHistory } from 'react-router-dom'; -import { ObservabilityApp } from '../../../typings/common'; -import { getDataHandler } from '../../data_handler'; +import { fetchHasData } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; -const fetchData = async (): Promise> => { - const apmPromise = getDataHandler('apm')?.hasData(); - const uptimePromise = getDataHandler('uptime')?.hasData(); - const logsPromise = getDataHandler('infra_logs')?.hasData(); - const metricsPromise = getDataHandler('infra_metrics')?.hasData(); - const [apm, uptime, logs, metrics] = await Promise.all([ - apmPromise, - uptimePromise, - logsPromise, - metricsPromise, - ]); - return { apm, uptime, infra_logs: logs, infra_metrics: metrics }; -}; - -export const Home = () => { +export const HomePage = () => { const history = useHistory(); - const { data = {} } = useFetcher(fetchData, []); + const { data = {} } = useFetcher(() => fetchHasData(), []); const values = Object.values(data); const hasSomeData = values.length ? values.some((hasData) => hasData) : null; if (hasSomeData === true) { - history.push({ pathname: '/overview', state: { hasData: data } }); + history.push({ pathname: '/dashboard' }); } if (hasSomeData === false) { - history.push({ pathname: '/start' }); + history.push({ pathname: '/landing' }); } return <>; diff --git a/x-pack/plugins/observability/public/pages/start/index.tsx b/x-pack/plugins/observability/public/pages/landing/index.tsx similarity index 99% rename from x-pack/plugins/observability/public/pages/start/index.tsx rename to x-pack/plugins/observability/public/pages/landing/index.tsx index fa48653cb8e4d..a256bb844c8a9 100644 --- a/x-pack/plugins/observability/public/pages/start/index.tsx +++ b/x-pack/plugins/observability/public/pages/landing/index.tsx @@ -27,7 +27,7 @@ const EuiCardWithoutPadding = styled(EuiCard)` padding: 0; `; -export const Start = () => { +export const LandingPage = () => { const { core } = usePluginContext(); const theme = useContext(ThemeContext); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index f60453da95307..efbb14a9bde06 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -47,8 +47,8 @@ export class Plugin implements PluginClass { hasData: () => new Promise((resolve) => { setTimeout(() => { - resolve(false); - }, 5000); + resolve(true); + }, 2000); }), }); registerDataHandler({ diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index e2df6631340ee..49147cfec96d2 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -5,9 +5,9 @@ */ import React from 'react'; import * as t from 'io-ts'; -import { Start } from '../pages/start'; -import { Overview } from '../pages/overview'; -import { Home } from '../pages/home'; +import { HomePage } from '../pages/home'; +import { LandingPage } from '../pages/landing'; +import { DashboardPage } from '../pages/dashboard'; export type RouteParams = DecodeParams; @@ -22,19 +22,19 @@ export interface Params { export const routes = { '/': { handler: () => { - return ; + return ; }, params: {}, }, - '/start': { + '/landing': { handler: () => { - return ; + return ; }, params: {}, }, - '/overview': { + '/dashboard': { handler: ({ query }: any) => { - return ; + return ; }, params: { query: t.partial({ From ce07d77a80a937b820a1037d2ce504ce0e02e27e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 29 Jun 2020 16:36:04 +0200 Subject: [PATCH 53/97] adding loading indicator to chart --- .../app/chart_container/index.test.tsx | 27 ++++ .../components/app/chart_container/index.tsx | 39 ++++++ .../components/app/section/apm/index.tsx | 67 +++++----- .../components/app/section/logs/index.tsx | 120 +++++++++--------- .../components/app/section/metrics/index.tsx | 46 +++---- .../components/app/section/uptime/index.tsx | 97 +++++++------- x-pack/plugins/observability/public/plugin.ts | 4 +- 7 files changed, 239 insertions(+), 161 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx create mode 100644 x-pack/plugins/observability/public/components/app/chart_container/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx new file mode 100644 index 0000000000000..37fccd3903e8d --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { queryAllByTestId, render } from '@testing-library/react'; +import React from 'react'; +import { ChartContainer } from './'; + +describe('chart container', () => { + it('shows loading indicator', () => { + const component = render( + +
My amazing component
+
+ ); + expect(component.getByTestId('loading')).toBeInTheDocument(); + }); + it("doesn't show loading indicator", () => { + const component = render( + +
My amazing component
+
+ ); + expect(queryAllByTestId(component.container, 'loading')).toEqual([]); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx new file mode 100644 index 0000000000000..0194cecd997bf --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { EuiLoadingChart } from '@elastic/eui'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiLoadingChartSize } from '@elastic/eui/src/components/loading/loading_chart'; + +interface Props { + isLoading: boolean; + height: number; + width?: number; + iconSize?: EuiLoadingChartSize; + children: React.ReactNode; +} + +export const ChartContainer = ({ isLoading, height, children, iconSize = 'xl', width }: Props) => { + const style = { height, marginTop: `-${height}px`, marginBottom: 0, width }; + return ( + <> + {children} + {isLoading === true && ( + + + + + + )} + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index a51bfe77ac92b..208ed310a15ed 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -26,6 +26,7 @@ import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { ChartContainer } from '../../chart_container'; interface Props { startTime?: string; @@ -58,6 +59,8 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { } }; + const isLoading = status === FETCH_STATUS.LOADING; + return ( { title={formatStatValue(stat)} description={stat.label} titleSize="s" - isLoading={status === FETCH_STATUS.LOADING} + isLoading={isLoading} />
); })} - - {}} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend={true} - legendPosition="bottom" - xDomain={{ min, max }} - /> - {transactionSeries?.coordinates && ( - <> - - numeral(d).format('0a')} - /> - - - )} - + + + {}} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend={true} + legendPosition="bottom" + xDomain={{ min, max }} + /> + {transactionSeries?.coordinates && ( + <> + + numeral(d).format('0a')} + /> + + + )} + + ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 7f4ff9f3d1553..e7cd8d371ba56 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -4,31 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, Fragment } from 'react'; -import d3 from 'd3'; import { + Axis, + BarSeries, Chart, - Settings, DARK_THEME, LIGHT_THEME, - BarSeries, - ScaleType, niceTimeFormatter, - Axis, Position, + ScaleType, + Settings, } from '@elastic/charts'; -import { ThemeContext } from 'styled-components'; -import { euiPaletteColorBlind } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { EuiFlexGroup } from '@elastic/eui'; -import { EuiFlexItem } from '@elastic/eui'; -import { EuiStat } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; -import { formatStatValue } from '../../../../utils/format_stat_value'; +import d3 from 'd3'; +import React, { Fragment, useContext } from 'react'; +import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; -import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; import { getDataHandler } from '../../../../data_handler'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { formatStatValue } from '../../../../utils/format_stat_value'; +import { ChartContainer } from '../../chart_container'; interface Props { startTime?: string; @@ -64,6 +62,8 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { }, }; + const isLoading = status === FETCH_STATUS.LOADING; + return ( { title={formatStatValue(stat)} description={stat.label} titleSize="s" - isLoading={status === FETCH_STATUS.LOADING} + isLoading={isLoading} /> ); })} - - {}} - theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} - showLegend - legendPosition="bottom" - xDomain={{ min, max }} - /> - {data && - Object.keys(data.series).map((key) => { - const serie = data.series[key]; - const chartData = serie.coordinates.map((coordinate) => ({ - ...coordinate, - g: serie.label, - })); - return ( - - - - numeral(d).format('0a')} - /> - - ); - })} - + + + {}} + theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} + showLegend + legendPosition="bottom" + xDomain={{ min, max }} + /> + {data && + Object.keys(data.series).map((key) => { + const serie = data.series[key]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); + return ( + + + + numeral(d).format('0a')} + /> + + ); + })} + + ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index a03c254221549..8c1775c2c9c5d 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -14,6 +14,7 @@ import { SectionContainer } from '../'; import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { getDataHandler } from '../../../../data_handler'; +import { ChartContainer } from '../../chart_container'; interface Props { startTime?: string; @@ -28,6 +29,8 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { } }, [startTime, endTime, bucketSize]); + const isLoading = status === FETCH_STATUS.LOADING; + return ( { const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; const chart = serie ? ( - + ) : ( <> @@ -57,12 +60,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { return ( - + {statKey !== 'hosts' && chart} @@ -73,24 +71,26 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { ); }; -const AreaChart = ({ serie }: { serie: Series }) => { +const AreaChart = ({ serie, isLoading }: { serie: Series; isLoading: boolean }) => { const theme = useContext(ThemeContext); return ( - - - - + + + + + + ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 62a7b5a159af0..1fbbc84af3de9 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -26,6 +26,7 @@ import { SectionContainer } from '../'; import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { getDataHandler } from '../../../../data_handler'; +import { ChartContainer } from '../../chart_container'; interface Props { startTime?: string; @@ -59,6 +60,8 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { } }; + const isLoading = status === FETCH_STATUS.LOADING; + return ( { title={formatStatValue(stat)} description={stat.label} titleSize="s" - isLoading={status === FETCH_STATUS.LOADING} + isLoading={isLoading} /> ); })} - - {}} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" - xDomain={{ min, max }} - /> - {data && - Object.keys(data.series).map((key) => { - const serie = data.series[key as keyof UptimeFetchDataResponse['series']]; - const chartData = serie.coordinates.map((coordinate) => ({ - ...coordinate, - g: serie.label, - })); - return ( - - - - numeral(x).format('0a')} - /> - - ); - })} - + + + {}} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + showLegend + legendPosition="bottom" + xDomain={{ min, max }} + /> + {data && + Object.keys(data.series).map((key) => { + const serie = data.series[key as keyof UptimeFetchDataResponse['series']]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); + return ( + + + + numeral(x).format('0a')} + /> + + ); + })} + + ); }; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index efbb14a9bde06..0e7d7c0bfaf0e 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -54,12 +54,12 @@ export class Plugin implements PluginClass { registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(false), + hasData: () => Promise.resolve(true), }); registerDataHandler({ appName: 'uptime', fetchData: fetchUptimeData, - hasData: () => Promise.resolve(false), + hasData: () => Promise.resolve(true), }); return { From 863823abbb5eb7b419753d5e917e9536f530b978 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 29 Jun 2020 16:54:16 +0200 Subject: [PATCH 54/97] fixing uptime chart --- .../public/components/app/section/uptime/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 1fbbc84af3de9..18c2bc6648095 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -39,7 +39,7 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { - return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize }); + return getDataHandler('uptime')?.fetchData({ startTime, endTime, bucketSize }); } }, [startTime, endTime, bucketSize]); From 7f16ae21d9b1ad0c6360caab30d4860d7f77e4e6 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 29 Jun 2020 18:28:32 +0200 Subject: [PATCH 55/97] adding brush functionality to charts --- .../components/app/section/apm/index.tsx | 20 ++++++---- .../components/app/section/helper.test.ts | 37 +++++++++++++++++++ .../public/components/app/section/helper.ts | 29 +++++++++++++++ .../components/app/section/logs/index.tsx | 5 ++- .../components/app/section/metrics/index.tsx | 9 ++--- .../components/app/section/uptime/index.tsx | 11 ++++-- .../observability/public/mock/logs.mock.ts | 2 +- .../public/pages/dashboard/index.tsx | 7 +--- 8 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/section/helper.test.ts create mode 100644 x-pack/plugins/observability/public/components/app/section/helper.ts diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 208ed310a15ed..ad31786d20e87 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -11,22 +11,24 @@ import { DARK_THEME, LIGHT_THEME, niceTimeFormatter, - Settings, - ScaleType, Position, + ScaleType, + Settings, } from '@elastic/charts'; -import d3 from 'd3'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import d3 from 'd3'; import React, { useContext } from 'react'; +import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { getDataHandler } from '../../../../data_handler'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; -import { formatStatValue } from '../../../../utils/format_stat_value'; import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; +import { onBrushEnd } from '../helper'; interface Props { startTime?: string; @@ -36,6 +38,8 @@ interface Props { export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); + const history = useHistory(); + const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { return getDataHandler('apm')?.fetchData({ startTime, endTime, bucketSize }); @@ -89,7 +93,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { {}} + onBrushEnd={({ x }) => onBrushEnd({ x, history })} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} showLegend={true} legendPosition="bottom" diff --git a/x-pack/plugins/observability/public/components/app/section/helper.test.ts b/x-pack/plugins/observability/public/components/app/section/helper.test.ts new file mode 100644 index 0000000000000..6a8cd27753a8d --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/helper.test.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { onBrushEnd } from './helper'; +import { History } from 'history'; + +describe('Chart helper', () => { + describe('onBrushEnd', () => { + const history = ({ + push: jest.fn(), + location: { + search: '', + }, + } as unknown) as History; + it("doesn't push a new history when x is not defined", () => { + onBrushEnd({ x: undefined, history }); + expect(history.push).not.toBeCalled(); + }); + + it('pushes a new history with time range converted to ISO', () => { + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: 'rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + + it('pushes a new history keeping current search', () => { + history.location.search = '?foo=bar'; + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: 'foo=bar&rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/helper.ts b/x-pack/plugins/observability/public/components/app/section/helper.ts new file mode 100644 index 0000000000000..81fa92cb87782 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/helper.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { XYBrushArea } from '@elastic/charts'; +import { History } from 'history'; +import { fromQuery, toQuery } from '../../../utils/url'; + +export const onBrushEnd = ({ x, history }: { x: XYBrushArea['x']; history: History }) => { + if (x) { + const start = x[0]; + const end = x[1]; + + const currentSearch = toQuery(history.location.search); + const nextSearch = { + rangeFrom: new Date(start).toISOString(), + rangeTo: new Date(end).toISOString(), + }; + history.push({ + ...history.location, + search: fromQuery({ + ...currentSearch, + ...nextSearch, + }), + }); + } +}; diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index e7cd8d371ba56..f44e61d697a8d 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -20,6 +20,7 @@ import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import React, { Fragment, useContext } from 'react'; +import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; @@ -27,6 +28,7 @@ import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; +import { onBrushEnd } from '../helper'; interface Props { startTime?: string; @@ -36,6 +38,7 @@ interface Props { export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); + const history = useHistory(); const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { @@ -92,7 +95,7 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { {}} + onBrushEnd={({ x }) => onBrushEnd({ x, history })} theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} showLegend legendPosition="bottom" diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 8c1775c2c9c5d..d955d8c59abfe 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiStat } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiStat } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { EuiSpacer } from '@elastic/eui'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; -import { getDataHandler } from '../../../../data_handler'; import { ChartContainer } from '../../chart_container'; interface Props { diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 18c2bc6648095..897be983c3ee6 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -17,16 +17,18 @@ import { } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import React, { Fragment, useContext } from 'react'; +import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; -import { getDataHandler } from '../../../../data_handler'; import { ChartContainer } from '../../chart_container'; +import { onBrushEnd } from '../helper'; interface Props { startTime?: string; @@ -36,6 +38,7 @@ interface Props { export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); + const history = useHistory(); const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { @@ -90,7 +93,7 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { {}} + onBrushEnd={({ x }) => onBrushEnd({ x, history })} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} showLegend legendPosition="bottom" diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index 1386980d5d3e1..5e0c9f99e1785 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -12,7 +12,7 @@ export const fetchLogsData: FetchData = () => { return new Promise((resolve) => { setTimeout(() => { resolve(response); - }, 5000); + }, 1000); }); }; diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index d6de60c6a54a3..efe1c200326ce 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -6,10 +6,7 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import moment from 'moment'; import React, { useContext } from 'react'; -import { useLocation } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; -import { EuiLoadingSpinner } from '@elastic/eui'; -import { ObservabilityApp } from '../../../typings/common'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { APMSection } from '../../components/app/section/apm'; @@ -17,13 +14,13 @@ import { LogsSection } from '../../components/app/section/logs'; import { MetricsSection } from '../../components/app/section/metrics'; import { UptimeSection } from '../../components/app/section/uptime'; import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; +import { fetchHasData } from '../../data_handler'; +import { useFetcher } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { appsSection } from '../home/section'; -import { useFetcher, FETCH_STATUS } from '../../hooks/use_fetcher'; -import { fetchHasData } from '../../data_handler'; interface Props { routeParams: RouteParams<'/dashboard'>; From d4771f6044f81ff5ebbd7733546c461dbd74e479 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 11:52:33 +0200 Subject: [PATCH 56/97] fixing refresh button and auto refresh function --- .../public/application/index.tsx | 2 +- .../components/shared/data_picker/index.tsx | 35 +++++-------------- .../public/hooks/use_query_params.tsx | 1 - .../public/hooks/use_url_params.tsx | 9 +++-- .../public/pages/dashboard/index.tsx | 23 ++++++++++-- .../observability/public/routes/index.tsx | 3 ++ .../observability/public/routes/json_rt.ts | 1 - 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index a5560b8b25cbf..49433b3a9cfaf 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -21,7 +21,7 @@ const App = () => { const path = key as keyof typeof routes; const route = routes[path]; const Wrapper = () => { - const { query, path: pathParams } = useUrlParams(route); + const { query, path: pathParams } = useUrlParams(route.params); return route.handler({ query, path: pathParams }); }; return ; diff --git a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx index eeecd405e505b..cc77c1ed72b4a 100644 --- a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx @@ -8,7 +8,6 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import React from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings'; -import { useQueryParams } from '../../../hooks/use_query_params'; import { fromQuery, toQuery } from '../../../utils/url'; export interface TimePickerTime { @@ -16,38 +15,29 @@ export interface TimePickerTime { to: string; } -interface TimePickerQuickRange extends TimePickerTime { +export interface TimePickerQuickRange extends TimePickerTime { display: string; } -interface TimePickerRefreshInterval { +export interface TimePickerRefreshInterval { pause: boolean; value: number; } -interface QueryParams { +interface Props { rangeFrom: string; rangeTo: string; refreshPaused: boolean; refreshInterval: number; } -interface Props { - rangeFrom: string; - rangeTo: string; -} - -export const DatePicker = ({ rangeFrom, rangeTo }: Props) => { +export const DatePicker = ({ rangeFrom, rangeTo, refreshPaused, refreshInterval }: Props) => { const location = useLocation(); const history = useHistory(); - // TODO: check if it can be done in another way const timePickerQuickRanges = useKibanaUISettings( UI_SETTINGS.TIMEPICKER_QUICK_RANGES ); - const timePickerRefreshInterval = useKibanaUISettings( - UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS - ); const commonlyUsedRanges = timePickerQuickRanges.map(({ from, to, display }) => ({ start: from, @@ -72,35 +62,28 @@ export const DatePicker = ({ rangeFrom, rangeTo }: Props) => { function onRefreshChange({ isPaused, - refreshInterval, + refreshInterval: interval, }: { isPaused: boolean; refreshInterval: number; }) { - updateUrl({ refreshPaused: isPaused, refreshInterval }); + updateUrl({ refreshPaused: isPaused, refreshInterval: interval }); } function onTimeChange({ start, end }: { start: string; end: string }) { updateUrl({ rangeFrom: start, rangeTo: end }); } - // TODO: maybe use Generics to specify what this component expects - const { - refreshPaused = timePickerRefreshInterval.pause, - refreshInterval = timePickerRefreshInterval.value, - } = useQueryParams(); - return ( ); }; diff --git a/x-pack/plugins/observability/public/hooks/use_query_params.tsx b/x-pack/plugins/observability/public/hooks/use_query_params.tsx index eea04ec8831e0..1b8c312cd0f75 100644 --- a/x-pack/plugins/observability/public/hooks/use_query_params.tsx +++ b/x-pack/plugins/observability/public/hooks/use_query_params.tsx @@ -11,6 +11,5 @@ export function useQueryParams(): T { urlSearchParms.forEach((value, key) => { params[key] = value; }); - // TODO: check it later return (params as unknown) as T; } diff --git a/x-pack/plugins/observability/public/hooks/use_url_params.tsx b/x-pack/plugins/observability/public/hooks/use_url_params.tsx index 0f5adbc46f1c7..acb05232ee47b 100644 --- a/x-pack/plugins/observability/public/hooks/use_url_params.tsx +++ b/x-pack/plugins/observability/public/hooks/use_url_params.tsx @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { useLocation, useParams } from 'react-router-dom'; import { isLeft } from 'fp-ts/lib/Either'; import { PathReporter } from 'io-ts/lib/PathReporter'; -import { routes } from '../routes'; +import { Params } from '../routes'; function getQueryParams(location: ReturnType) { const urlSearchParms = new URLSearchParams(location.search); @@ -18,15 +18,14 @@ function getQueryParams(location: ReturnType) { return queryParams; } -export function useUrlParams(route: typeof routes[T]) { +export function useUrlParams(params: Params) { const location = useLocation(); const pathParams = useParams(); const queryParams = getQueryParams(location); - const { params } = route; const rts = { - queryRt: 'query' in params ? t.exact(params.query) : t.strict({}), - pathRt: 'path' in params ? t.exact(params.path) : t.strict({}), + queryRt: params.query ? t.exact(params.query) : t.strict({}), + pathRt: params.path ? t.exact(params.path) : t.strict({}), }; const queryResult = rts.queryRt.decode(queryParams); diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index efe1c200326ce..84dd84ba3acfb 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -13,7 +13,11 @@ import { APMSection } from '../../components/app/section/apm'; import { LogsSection } from '../../components/app/section/logs'; import { MetricsSection } from '../../components/app/section/metrics'; import { UptimeSection } from '../../components/app/section/uptime'; -import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; +import { + DatePicker, + TimePickerTime, + TimePickerRefreshInterval, +} from '../../components/shared/data_picker'; import { fetchHasData } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; @@ -32,8 +36,16 @@ export const DashboardPage = ({ routeParams }: Props) => { const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); + const timePickerRefreshInterval = useKibanaUISettings( + UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS + ); - const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to } = routeParams.query; + const { + rangeFrom = timePickerTime.from, + rangeTo = timePickerTime.to, + refreshInterval = timePickerRefreshInterval.value, + refreshPaused = timePickerRefreshInterval.pause, + } = routeParams.query; const startTime = getParsedDate(rangeFrom); const endTime = getParsedDate(rangeTo, { roundUp: true }); @@ -53,7 +65,12 @@ export const DashboardPage = ({ routeParams }: Props) => { > - + diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 49147cfec96d2..26c2c95d3edd2 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -8,6 +8,7 @@ import * as t from 'io-ts'; import { HomePage } from '../pages/home'; import { LandingPage } from '../pages/landing'; import { DashboardPage } from '../pages/dashboard'; +import { jsonRt } from './json_rt'; export type RouteParams = DecodeParams; @@ -40,6 +41,8 @@ export const routes = { query: t.partial({ rangeFrom: t.string, rangeTo: t.string, + refreshPaused: jsonRt.pipe(t.boolean), + refreshInterval: jsonRt.pipe(t.number), }), }, }, diff --git a/x-pack/plugins/observability/public/routes/json_rt.ts b/x-pack/plugins/observability/public/routes/json_rt.ts index c8ce1eb669a6b..fcc73547a686b 100644 --- a/x-pack/plugins/observability/public/routes/json_rt.ts +++ b/x-pack/plugins/observability/public/routes/json_rt.ts @@ -12,7 +12,6 @@ export const jsonRt = new t.Type( (input, context) => either.chain(t.string.validate(input, context), (str) => { try { - console.log('#############3', str); return t.success(JSON.parse(str)); } catch (e) { return t.failure(input, context); From 88ee16661d803ceb4a2a7538f9e9fb9309a23982 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 12:46:48 +0200 Subject: [PATCH 57/97] adding horizontal line to accordion section --- .../components/app/section/index.test.tsx | 38 +++++++++++++++++++ .../public/components/app/section/index.tsx | 17 ++++++++- .../public/pages/dashboard/index.tsx | 3 +- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/section/index.test.tsx diff --git a/x-pack/plugins/observability/public/components/app/section/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/index.test.tsx new file mode 100644 index 0000000000000..6d64e77100bff --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/index.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { SectionContainer } from './'; +import { render } from '@testing-library/react'; +import { EuiThemeProvider } from '../../../typings'; + +describe('SectionContainer', () => { + it('renders section without app link', () => { + const component = render( + + +
I am a very nice component
+
+
+ ); + expect(component.getByText('I am a very nice component')).toBeInTheDocument(); + expect(component.getByText('Foo')).toBeInTheDocument(); + expect(component.getByText('foo bar')).toBeInTheDocument(); + expect(component.queryAllByText('View in app')).toEqual([]); + }); + it('renders section with app link', () => { + const component = render( + + +
I am a very nice component
+
+
+ ); + expect(component.getByText('I am a very nice component')).toBeInTheDocument(); + expect(component.getByText('Foo')).toBeInTheDocument(); + expect(component.getByText('foo bar')).toBeInTheDocument(); + expect(component.getByText('View in app')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index b48d8836fb9fa..3a9e83fc0c882 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; +import styled from 'styled-components'; interface Props { title: string; @@ -16,10 +17,22 @@ interface Props { appLink?: string; children: React.ReactNode; } +const StyledEuiAccordion = styled(EuiAccordion)` + .euiAccordion__triggerWrapper { + border-bottom: ${(props) => props.theme.eui.euiBorderThin}; + } + .euiAccordion__button, + .euiAccordion__optionalAction { + margin-bottom: 16px; + } + .euiAccordion__childWrapper { + margin-top: 16px; + } +`; export const SectionContainer = ({ title, appLink, children, subtitle, minHeight }: Props) => { return ( - {children} - + ); }; diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index 84dd84ba3acfb..ba42e6551f46c 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -7,6 +7,7 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui' import moment from 'moment'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; +import { EuiHorizontalRule } from '@elastic/eui'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { APMSection } from '../../components/app/section/apm'; @@ -74,7 +75,7 @@ export const DashboardPage = ({ routeParams }: Props) => { - + {hasData?.infra_logs && ( From f6869616de674d27ca543226fe8ff7902f013555 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 13:04:19 +0200 Subject: [PATCH 58/97] adding emptySection to dashboard page --- .../components/app/empty_section/index.tsx | 2 +- .../public/pages/dashboard/emptySection.ts | 70 +++++++++++++++++++ .../public/pages/dashboard/index.tsx | 4 +- .../public/pages/home/section.ts | 24 +------ .../public/typings/section/index.ts | 17 +++++ 5 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/dashboard/emptySection.ts create mode 100644 x-pack/plugins/observability/public/typings/section/index.ts diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx index 05747e945dbd5..ad1a1a302a529 100644 --- a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx @@ -8,7 +8,7 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import { EuiButton } from '@elastic/eui'; import { ThemeContext } from 'styled-components'; import { EuiText } from '@elastic/eui'; -import { ISection } from '../../../pages/home/section'; +import { ISection } from '../../../typings/section'; interface Props { section: ISection; diff --git a/x-pack/plugins/observability/public/pages/dashboard/emptySection.ts b/x-pack/plugins/observability/public/pages/dashboard/emptySection.ts new file mode 100644 index 0000000000000..16a21b3ffd7ec --- /dev/null +++ b/x-pack/plugins/observability/public/pages/dashboard/emptySection.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { ISection } from '../../typings/section'; + +export const emptySections: ISection[] = [ + { + id: 'infra_logs', + title: i18n.translate('xpack.observability.emptySection.apps.logs.title', { + defaultMessage: 'Logs', + }), + icon: 'logoLogging', + description: i18n.translate('xpack.observability.emptySection.apps.logs.description', { + defaultMessage: + 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.logs.link', { + defaultMessage: 'Install Filebeat', + }), + href: 'https://www.elastic.co', + }, + { + id: 'apm', + title: i18n.translate('xpack.observability.emptySection.apps.apm.title', { + defaultMessage: 'APM', + }), + icon: 'logoAPM', + description: i18n.translate('xpack.observability.emptySection.apps.apm.description', { + defaultMessage: + 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.apm.link', { + defaultMessage: 'Install agent', + }), + href: 'https://www.elastic.co', + }, + { + id: 'infra_metrics', + title: i18n.translate('xpack.observability.emptySection.apps.metrics.title', { + defaultMessage: 'Metrics', + }), + icon: 'logoMetrics', + description: i18n.translate('xpack.observability.emptySection.apps.metrics.description', { + defaultMessage: + 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.metrics.link', { + defaultMessage: 'Install metrics module', + }), + href: 'https://www.elastic.co', + }, + { + id: 'uptime', + title: i18n.translate('xpack.observability.emptySection.apps.uptime.title', { + defaultMessage: 'Uptime', + }), + icon: 'logoUptime', + description: i18n.translate('xpack.observability.emptySection.apps.uptime.description', { + defaultMessage: + 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.uptime.link', { + defaultMessage: 'Install Heartbeat', + }), + href: 'https://www.elastic.co', + }, +]; diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index ba42e6551f46c..d8cca9ccdd169 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -25,7 +25,7 @@ import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_sett import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; -import { appsSection } from '../home/section'; +import { emptySections } from './emptySection'; interface Props { routeParams: RouteParams<'/dashboard'>; @@ -119,7 +119,7 @@ export const DashboardPage = ({ routeParams }: Props) => { - {appsSection + {emptySections .filter(({ id }) => hasData && !hasData[id]) .map((app) => { return ( diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index e44e73a89a29f..8c87f17c16b3d 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -4,17 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { ObservabilityApp } from '../../../typings/common'; - -export interface ISection { - id: ObservabilityApp; - title: string; - icon: string; - description: string; - href?: string; - linkTitle?: string; - target?: '_blank'; -} +import { ISection } from '../../typings/section'; export const appsSection: ISection[] = [ { @@ -27,9 +17,6 @@ export const appsSection: ISection[] = [ defaultMessage: 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', }), - linkTitle: i18n.translate('xpack.observability.section.apps.logs.link', { - defaultMessage: 'Install Filebeat', - }), href: 'https://www.elastic.co', }, { @@ -42,9 +29,6 @@ export const appsSection: ISection[] = [ defaultMessage: 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', }), - linkTitle: i18n.translate('xpack.observability.section.apps.apm.link', { - defaultMessage: 'Install agent', - }), href: 'https://www.elastic.co', }, { @@ -57,9 +41,6 @@ export const appsSection: ISection[] = [ defaultMessage: 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', }), - linkTitle: i18n.translate('xpack.observability.section.apps.metrics.link', { - defaultMessage: 'Install metrics module', - }), href: 'https://www.elastic.co', }, { @@ -72,9 +53,6 @@ export const appsSection: ISection[] = [ defaultMessage: 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', }), - linkTitle: i18n.translate('xpack.observability.section.apps.uptime.link', { - defaultMessage: 'Install Heartbeat', - }), href: 'https://www.elastic.co', }, ]; diff --git a/x-pack/plugins/observability/public/typings/section/index.ts b/x-pack/plugins/observability/public/typings/section/index.ts new file mode 100644 index 0000000000000..7e6eec771af52 --- /dev/null +++ b/x-pack/plugins/observability/public/typings/section/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ObservabilityApp } from '../../../typings/common'; + +export interface ISection { + id: ObservabilityApp; + title: string; + icon: string; + description: string; + href?: string; + linkTitle?: string; + target?: '_blank'; +} From 414f05c828db97a636faea808047603e689fe1a7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 13:45:50 +0200 Subject: [PATCH 59/97] adding add data button --- .../components/app/header/index.test.tsx | 32 +++++++++++++++++++ .../public/components/app/header/index.tsx | 18 ++++++++--- .../components/app/layout/with_header.tsx | 16 ++++++---- .../public/components/app/section/index.tsx | 2 +- .../components/app/section/metrics/index.tsx | 2 +- .../public/pages/dashboard/index.tsx | 1 + 6 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/header/index.test.tsx diff --git a/x-pack/plugins/observability/public/components/app/header/index.test.tsx b/x-pack/plugins/observability/public/components/app/header/index.test.tsx new file mode 100644 index 0000000000000..bb56058bf7697 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/header/index.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { Header } from './'; +import { render } from '@testing-library/react'; +import { EuiThemeProvider } from '../../../typings'; + +describe('Header', () => { + it('renders without add data button', () => { + const { getByText, queryAllByText, getByTestId } = render( + +
+ + ); + expect(getByTestId('observability-logo')).toBeInTheDocument(); + expect(getByText('Observability')).toBeInTheDocument(); + expect(queryAllByText('Add data')).toEqual([]); + }); + it('renders with add data button', () => { + const { getByText, getByTestId } = render( + +
+ + ); + expect(getByTestId('observability-logo')).toBeInTheDocument(); + expect(getByText('Observability')).toBeInTheDocument(); + expect(getByText('Add data')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx index ffd55ce01b940..390bc53409158 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; +import { EuiButtonEmpty } from '@elastic/eui'; const Container = styled.div<{ color: string }>` background: ${(props) => props.color}; @@ -24,17 +25,18 @@ const Wrapper = styled.div<{ restrictWidth?: number }>` interface Props { color: string; + showAddData?: boolean; restrictWidth?: number; } -export const Header = ({ color, restrictWidth }: Props) => { +export const Header = ({ color, restrictWidth, showAddData = false }: Props) => { return ( - + - + @@ -45,8 +47,16 @@ export const Header = ({ color, restrictWidth }: Props) => { + {showAddData && ( + + {/* TODO: caue: what is the URL here? */} + + {i18n.translate('xpack.observability.home.addData', { defaultMessage: 'Add data' })} + + + )} - + ); diff --git a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx index af231a6f6accd..72ef48a00eebc 100644 --- a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx +++ b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx @@ -28,16 +28,20 @@ interface Props { bodyColor: string; children?: React.ReactNode; restrictWidth?: number; + showAddData?: boolean; } -export const WithHeaderLayout = ({ headerColor, bodyColor, children, restrictWidth }: Props) => ( +export const WithHeaderLayout = ({ + headerColor, + bodyColor, + children, + restrictWidth, + showAddData, +}: Props) => ( -
+
- - - {children} - + {children} ); diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 3a9e83fc0c882..1e02e36b7d74a 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -26,7 +26,7 @@ const StyledEuiAccordion = styled(EuiAccordion)` margin-bottom: 16px; } .euiAccordion__childWrapper { - margin-top: 16px; + margin-top: 8px; } `; diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index d955d8c59abfe..6d98dbc8e0cbd 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -53,7 +53,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { ) : ( <> - + ); diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index d8cca9ccdd169..4a058b0344486 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -63,6 +63,7 @@ export const DashboardPage = ({ routeParams }: Props) => { From 90572fa60879d931bb5b83d763f9a539c29c53bd Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 14:22:54 +0200 Subject: [PATCH 60/97] adding resources section --- .../components/app/resources/index.test.tsx | 29 +++++++ .../public/components/app/resources/index.tsx | 65 ++++++++++++++ .../public/pages/dashboard/index.tsx | 84 ++++++++++--------- 3 files changed, 140 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/resources/index.test.tsx create mode 100644 x-pack/plugins/observability/public/components/app/resources/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/resources/index.test.tsx b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx new file mode 100644 index 0000000000000..7df8fc4e13cae --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { render } from '@testing-library/react'; +import React from 'react'; +import { Resources } from './'; + +describe('Resources', () => { + it('renders resources with all elements', () => { + const { getByText } = render(); + expect(getByText('Documentation')).toBeInTheDocument(); + expect(getByText('Discuss forum')).toBeInTheDocument(); + expect(getByText('Training and webinars')).toBeInTheDocument(); + }); + it('expects all links to be defined', () => { + const { getByTestId } = render(); + const validateLink = (testId: string) => { + const href = (getByTestId(testId) as HTMLAnchorElement).href; + expect(href).not.toEqual(''); + expect(href).not.toEqual('https://www.elastic.co/'); + }; + + validateLink('button-documentation'); + validateLink('button-forum'); + validateLink('button-training'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/resources/index.tsx b/x-pack/plugins/observability/public/components/app/resources/index.tsx new file mode 100644 index 0000000000000..0c5cb22d70cc2 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/resources/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +const resources = [ + { + id: 'documentation', + icon: 'documents', + name: i18n.translate('xpack.observability.resources.documentation', { + defaultMessage: 'Documentation', + }), + // TODO: caue what's the url? + href: 'https://www.elastic.co', + }, + { + id: 'forum', + icon: 'editorComment', + name: i18n.translate('xpack.observability.resources.forum', { + defaultMessage: 'Discuss forum', + }), + // TODO: caue what's the url? + href: 'https://www.elastic.co', + }, + { + id: 'training', + icon: 'training', + name: i18n.translate('xpack.observability.resources.training', { + defaultMessage: 'Training and webinars', + }), + // TODO: caue what's the url? + href: 'https://www.elastic.co', + }, +]; + +export const Resources = () => { + return ( + + + +

+ {i18n.translate('xpack.observability.resources.title', { + defaultMessage: 'Resources', + })} +

+
+
+ {resources.map((resource) => ( + + + {resource.name} + + + ))} +
+ ); +}; diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index 4a058b0344486..0b28a65c2b5bf 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -26,6 +26,7 @@ import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { emptySections } from './emptySection'; +import { Resources } from '../../components/app/resources'; interface Props { routeParams: RouteParams<'/dashboard'>; @@ -78,48 +79,55 @@ export const DashboardPage = ({ routeParams }: Props) => { - - {hasData?.infra_logs && ( - - - - )} - {hasData?.infra_metrics && ( - - - - )} - {hasData?.apm && ( - - - - )} - {hasData?.uptime && ( - - - - )} + + + + {hasData?.infra_logs && ( + + + + )} + {hasData?.infra_metrics && ( + + + + )} + {hasData?.apm && ( + + + + )} + {hasData?.uptime && ( + + + + )} + + + + + - + {emptySections .filter(({ id }) => hasData && !hasData[id]) .map((app) => { From 360f6fc86b4df8c22445999b8dabe40e94fe752e Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 14:48:02 +0200 Subject: [PATCH 61/97] removing margins from horizontal rule --- x-pack/plugins/observability/public/pages/dashboard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index 0b28a65c2b5bf..b3253edb1e3c1 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -77,7 +77,7 @@ export const DashboardPage = ({ routeParams }: Props) => {
- + From 3dc98dbce5a12594e298aa94611b46660f3801c6 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 16:16:13 +0200 Subject: [PATCH 62/97] changing min interval to 60s --- x-pack/plugins/observability/public/pages/dashboard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index b3253edb1e3c1..a0e13d2f6eaee 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -56,7 +56,7 @@ export const DashboardPage = ({ routeParams }: Props) => { ? getBucketSize({ start: moment.utc(startTime).valueOf(), end: moment.utc(endTime).valueOf(), - minInterval: 'auto', + minInterval: '60s', }) : undefined; From 1c6bc016a017895354577fa2ec4900d681cb3c53 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 18:02:46 +0200 Subject: [PATCH 63/97] fixing empty section --- .../public/pages/dashboard/index.tsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index a0e13d2f6eaee..8f373aeb2ee98 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -60,6 +60,8 @@ export const DashboardPage = ({ routeParams }: Props) => { }) : undefined; + const appEmptySections = emptySections.filter(({ id }) => hasData && !hasData[id]); + return ( { )} + {appEmptySections.length && ( + + + + {appEmptySections + .filter(({ id }) => hasData && !hasData[id]) + .map((app) => { + return ( + + + + ); + })} + + + )} + - - - - - {emptySections - .filter(({ id }) => hasData && !hasData[id]) - .map((app) => { - return ( - - - - ); - })} -
); }; From 708851fe2233fbbba977e46f485893f38be85dcf Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 30 Jun 2020 18:13:38 +0200 Subject: [PATCH 64/97] removing unnecessary code --- .../components/app/section/metrics/index.tsx | 8 +++++--- .../public/hooks/use_query_params.tsx | 15 --------------- .../public/typings/fetch_data_response/index.d.ts | 2 +- 3 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 x-pack/plugins/observability/public/hooks/use_query_params.tsx diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 6d98dbc8e0cbd..6dad5b35e824c 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { MetricsFetchDataResponse, Series } from '../../../../typings/fetch_data_response'; +import { MetricsFetchDataResponse } from '../../../../typings/fetch_data_response'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; @@ -21,6 +21,8 @@ interface Props { bucketSize?: string; } +type MetricsSeries = MetricsFetchDataResponse['series']; + export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { @@ -46,7 +48,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { const stat = data.stats[statKey]; const value = formatStatValue(stat); - const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; + const serie = data.series[key as keyof MetricsSeries]; const chart = serie ? ( @@ -70,7 +72,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { ); }; -const AreaChart = ({ serie, isLoading }: { serie: Series; isLoading: boolean }) => { +const AreaChart = ({ serie, isLoading }: { serie: MetricsSeries; isLoading: boolean }) => { const theme = useContext(ThemeContext); return ( diff --git a/x-pack/plugins/observability/public/hooks/use_query_params.tsx b/x-pack/plugins/observability/public/hooks/use_query_params.tsx deleted file mode 100644 index 1b8c312cd0f75..0000000000000 --- a/x-pack/plugins/observability/public/hooks/use_query_params.tsx +++ /dev/null @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { useLocation } from 'react-router-dom'; - -export function useQueryParams(): T { - const urlSearchParms = new URLSearchParams(useLocation().search); - const params: Record = {}; - urlSearchParms.forEach((value, key) => { - params[key] = value; - }); - return (params as unknown) as T; -} diff --git a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts index 06b5eb99a1dfa..06e86d1096cfc 100644 --- a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts +++ b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts @@ -16,7 +16,7 @@ export interface Coordinates { y?: number; } -export interface Series { +interface Series { label: string; coordinates: Coordinates[]; color?: string; From 74abf3a1ad7c61c537d0b70a1dce9c7a4d18bf0b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 09:54:48 +0200 Subject: [PATCH 65/97] adding unit tests --- .../app/empty_section/index.test.tsx | 50 +++++ .../components/app/empty_section/index.tsx | 9 +- .../components/app/section/apm/index.test.tsx | 58 ++++++ .../app/section/apm/mock_data/apm.mock.ts | 174 ++++++++++++++++++ .../public/pages/dashboard/index.tsx | 2 +- 5 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx create mode 100644 x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx create mode 100644 x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx new file mode 100644 index 0000000000000..720583546b7bb --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { render } from '@testing-library/react'; +import React from 'react'; +import { EuiThemeProvider } from '../../../typings'; +import { EmptySection } from './'; +import { ISection } from '../../../typings/section'; + +describe('EmptySection', () => { + it('renders without action button', () => { + const section: ISection = { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: 'foo bar', + }; + const { getByText, queryAllByText } = render( + + + + ); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByText('foo bar')).toBeInTheDocument(); + expect(queryAllByText('Install agent')).toEqual([]); + }); + it('renders with action button', () => { + const section: ISection = { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: 'foo bar', + linkTitle: 'install agent', + href: 'https://www.elastic.co', + }; + const { getByText, getByTestId } = render( + + + + ); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByText('foo bar')).toBeInTheDocument(); + const linkButton = getByTestId('empty-apm') as HTMLAnchorElement; + expect(linkButton.href).toEqual('https://www.elastic.co/'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx index ad1a1a302a529..00d5b49bfbbdc 100644 --- a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx @@ -31,7 +31,14 @@ export const EmptySection = ({ section }: Props) => { actions={ <> {section.linkTitle && ( - + {section.linkTitle} )} diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx new file mode 100644 index 0000000000000..6ef10dbc4ef61 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { APMSection } from './'; +import { response } from './mock_data/apm.mock'; +import { render } from '@testing-library/react'; +import { EuiThemeProvider } from '../../../../typings'; +import React from 'react'; +import * as fetcherHook from '../../../../hooks/use_fetcher'; + +describe('APMSection', () => { + it('renders with transaction series and stats', () => { + jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + data: response, + status: fetcherHook.FETCH_STATUS.SUCCESS, + refetch: jest.fn(), + }); + const { getByText, queryAllByTestId } = render( + + + + ); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByText('View in app')).toBeInTheDocument(); + expect(getByText('Services 11')).toBeInTheDocument(); + expect(getByText('Transactions 312k')).toBeInTheDocument(); + expect(queryAllByTestId('loading')).toEqual([]); + }); + it('shows loading state', () => { + jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + data: undefined, + status: fetcherHook.FETCH_STATUS.LOADING, + refetch: jest.fn(), + }); + const { getByText, queryAllByText, getByTestId } = render( + + + + ); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByTestId('loading')).toBeInTheDocument(); + expect(queryAllByText('View in app')).toEqual([]); + expect(queryAllByText('Services 11')).toEqual([]); + expect(queryAllByText('Transactions 312k')).toEqual([]); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts new file mode 100644 index 0000000000000..b60b569590508 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ApmFetchDataResponse } from '../../../../../typings/fetch_data_response'; + +export const response: ApmFetchDataResponse = { + title: i18n.translate('apm.observabilityDashboard.title', { + defaultMessage: 'APM', + }), + appLink: '/app/apm', + stats: { + services: { label: 'Services', value: 11, type: 'number' }, + transactions: { label: 'Transactions', value: 312000, type: 'number', color: 'euiColorVis1' }, + }, + series: { + transactions: { + label: i18n.translate('apm.observabilityDashboard.chart.transactions', { + defaultMessage: 'Transactions', + }), + color: 'euiColorVis1', + coordinates: [ + { x: 1591365600000, y: 32 }, + { x: 1591366200000, y: 43 }, + { x: 1591366800000, y: 22 }, + { x: 1591367400000, y: 29 }, + { x: 1591368000000, y: 39 }, + { x: 1591368600000, y: 36 }, + { x: 1591369200000, y: 50 }, + { x: 1591369800000, y: 31 }, + { x: 1591370400000, y: 39 }, + { x: 1591371000000, y: 26 }, + { x: 1591371600000, y: 45 }, + { x: 1591372200000, y: 27 }, + { x: 1591372800000, y: 37 }, + { x: 1591373400000, y: 55 }, + { x: 1591374000000, y: 31 }, + { x: 1591374600000, y: 26 }, + { x: 1591375200000, y: 57 }, + { x: 1591375800000, y: 25 }, + { x: 1591376400000, y: 28 }, + { x: 1591377000000, y: 40 }, + { x: 1591377600000, y: 33 }, + { x: 1591378200000, y: 33 }, + { x: 1591378800000, y: 31 }, + { x: 1591379400000, y: 32 }, + { x: 1591380000000, y: 34 }, + { x: 1591380600000, y: 31 }, + { x: 1591381200000, y: 16 }, + { x: 1591381800000, y: 34 }, + { x: 1591382400000, y: 33 }, + { x: 1591383000000, y: 35 }, + { x: 1591383600000, y: 47 }, + { x: 1591384200000, y: 44 }, + { x: 1591384800000, y: 21 }, + { x: 1591385400000, y: 25 }, + { x: 1591386000000, y: 34 }, + { x: 1591386600000, y: 37 }, + { x: 1591387200000, y: 38 }, + { x: 1591387800000, y: 28 }, + { x: 1591388400000, y: 32 }, + { x: 1591389000000, y: 37 }, + { x: 1591389600000, y: 25 }, + { x: 1591390200000, y: 33 }, + { x: 1591390800000, y: 34 }, + { x: 1591391400000, y: 30 }, + { x: 1591392000000, y: 45 }, + { x: 1591392600000, y: 42 }, + { x: 1591393200000, y: 23 }, + { x: 1591393800000, y: 33 }, + { x: 1591394400000, y: 38 }, + { x: 1591395000000, y: 30 }, + { x: 1591395600000, y: 25 }, + { x: 1591396200000, y: 33 }, + { x: 1591396800000, y: 37 }, + { x: 1591397400000, y: 43 }, + { x: 1591398000000, y: 30 }, + { x: 1591398600000, y: 36 }, + { x: 1591399200000, y: 28 }, + { x: 1591399800000, y: 39 }, + { x: 1591400400000, y: 27 }, + { x: 1591401000000, y: 41 }, + { x: 1591401600000, y: 25 }, + { x: 1591402200000, y: 31 }, + { x: 1591402800000, y: 28 }, + { x: 1591403400000, y: 29 }, + { x: 1591404000000, y: 49 }, + { x: 1591404600000, y: 24 }, + { x: 1591405200000, y: 41 }, + { x: 1591405800000, y: 30 }, + { x: 1591406400000, y: 36 }, + { x: 1591407000000, y: 39 }, + { x: 1591407600000, y: 23 }, + { x: 1591408200000, y: 40 }, + { x: 1591408800000, y: 34 }, + { x: 1591409400000, y: 28 }, + { x: 1591410000000, y: 33 }, + { x: 1591410600000, y: 31 }, + { x: 1591411200000, y: 39 }, + { x: 1591411800000, y: 33 }, + { x: 1591412400000, y: 35 }, + { x: 1591413000000, y: 31 }, + { x: 1591413600000, y: 35 }, + { x: 1591414200000, y: 37 }, + { x: 1591414800000, y: 26 }, + { x: 1591415400000, y: 27 }, + { x: 1591416000000, y: 26 }, + { x: 1591416600000, y: 34 }, + { x: 1591417200000, y: 33 }, + { x: 1591417800000, y: 38 }, + { x: 1591418400000, y: 34 }, + { x: 1591419000000, y: 37 }, + { x: 1591419600000, y: 24 }, + { x: 1591420200000, y: 25 }, + { x: 1591420800000, y: 20 }, + { x: 1591421400000, y: 35 }, + { x: 1591422000000, y: 41 }, + { x: 1591422600000, y: 40 }, + { x: 1591423200000, y: 33 }, + { x: 1591423800000, y: 24 }, + { x: 1591424400000, y: 44 }, + { x: 1591425000000, y: 24 }, + { x: 1591425600000, y: 32 }, + { x: 1591426200000, y: 37 }, + { x: 1591426800000, y: 34 }, + { x: 1591427400000, y: 28 }, + { x: 1591428000000, y: 26 }, + { x: 1591428600000, y: 37 }, + { x: 1591429200000, y: 36 }, + { x: 1591429800000, y: 37 }, + { x: 1591430400000, y: 23 }, + { x: 1591431000000, y: 47 }, + { x: 1591431600000, y: 41 }, + { x: 1591432200000, y: 24 }, + { x: 1591432800000, y: 34 }, + { x: 1591433400000, y: 27 }, + { x: 1591434000000, y: 34 }, + { x: 1591434600000, y: 44 }, + { x: 1591435200000, y: 20 }, + { x: 1591435800000, y: 34 }, + { x: 1591436400000, y: 29 }, + { x: 1591437000000, y: 28 }, + { x: 1591437600000, y: 36 }, + { x: 1591438200000, y: 34 }, + { x: 1591438800000, y: 26 }, + { x: 1591439400000, y: 29 }, + { x: 1591440000000, y: 45 }, + { x: 1591440600000, y: 34 }, + { x: 1591441200000, y: 25 }, + { x: 1591441800000, y: 34 }, + { x: 1591442400000, y: 28 }, + { x: 1591443000000, y: 34 }, + { x: 1591443600000, y: 31 }, + { x: 1591444200000, y: 24 }, + { x: 1591444800000, y: 34 }, + { x: 1591445400000, y: 21 }, + { x: 1591446000000, y: 40 }, + { x: 1591446600000, y: 37 }, + { x: 1591447200000, y: 31 }, + { x: 1591447800000, y: 21 }, + { x: 1591448400000, y: 24 }, + { x: 1591449000000, y: 30 }, + { x: 1591449600000, y: 22 }, + { x: 1591450200000, y: 27 }, + { x: 1591450800000, y: 30 }, + { x: 1591451400000, y: 22 }, + { x: 1591452000000, y: 9 }, + ], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index 8f373aeb2ee98..de0a33b9ee1d5 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -121,7 +121,7 @@ export const DashboardPage = ({ routeParams }: Props) => { )} - {appEmptySections.length && ( + {!!appEmptySections.length && ( From d835beb564ffc6bd59e44023cea2203d5d685c52 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 10:15:46 +0200 Subject: [PATCH 66/97] fixing imports --- .../public/components/app/layout/with_header.tsx | 2 +- .../public/components/app/section/apm/index.tsx | 2 +- .../components/app/section/apm/mock_data/apm.mock.ts | 2 +- .../public/components/app/section/logs/index.tsx | 2 +- .../public/components/app/section/metrics/index.tsx | 8 +++----- .../public/components/app/section/uptime/index.tsx | 2 +- x-pack/plugins/observability/public/mock/apm.mock.ts | 3 +-- x-pack/plugins/observability/public/mock/logs.mock.ts | 3 +-- x-pack/plugins/observability/public/mock/metrics.mock.ts | 3 +-- x-pack/plugins/observability/public/mock/uptime.mock.ts | 3 +-- .../public/typings/fetch_overview_data/index.ts | 4 ++-- .../observability/public/utils/format_stat_value.test.ts | 2 +- .../observability/public/utils/format_stat_value.ts | 2 +- 13 files changed, 16 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx index 72ef48a00eebc..06bf318206aa8 100644 --- a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx +++ b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiPage, EuiPageBody, EuiPageProps } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { EuiPage, EuiPageBody, EuiSpacer, EuiPageProps } from '@elastic/eui'; import { Header } from '../header/index'; const getPaddingSize = (props: EuiPageProps) => (props.restrictWidth ? 0 : '24px'); diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index ad31786d20e87..884b6d488b55e 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -25,7 +25,7 @@ import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { ApmFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { ApmFetchDataResponse } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; import { onBrushEnd } from '../helper'; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts index b60b569590508..affb23ea338a3 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts +++ b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ApmFetchDataResponse } from '../../../../../typings/fetch_data_response'; +import { ApmFetchDataResponse } from '../../../../../typings'; export const response: ApmFetchDataResponse = { title: i18n.translate('apm.observabilityDashboard.title', { diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index f44e61d697a8d..53877b9201469 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -25,7 +25,7 @@ import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { LogsFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { LogsFetchDataResponse } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; import { onBrushEnd } from '../helper'; diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 6dad5b35e824c..6e1099bb6ccd1 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { MetricsFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { MetricsFetchDataResponse, Series } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; @@ -21,8 +21,6 @@ interface Props { bucketSize?: string; } -type MetricsSeries = MetricsFetchDataResponse['series']; - export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { @@ -48,7 +46,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { const stat = data.stats[statKey]; const value = formatStatValue(stat); - const serie = data.series[key as keyof MetricsSeries]; + const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; const chart = serie ? ( @@ -72,7 +70,7 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { ); }; -const AreaChart = ({ serie, isLoading }: { serie: MetricsSeries; isLoading: boolean }) => { +const AreaChart = ({ serie, isLoading }: { serie: Series; isLoading: boolean }) => { const theme = useContext(ThemeContext); return ( diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 897be983c3ee6..2016527c065ec 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -25,7 +25,7 @@ import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { UptimeFetchDataResponse } from '../../../../typings/fetch_data_response'; +import { UptimeFetchDataResponse } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; import { onBrushEnd } from '../helper'; diff --git a/x-pack/plugins/observability/public/mock/apm.mock.ts b/x-pack/plugins/observability/public/mock/apm.mock.ts index 1f172776f9130..d5b7f6349e586 100644 --- a/x-pack/plugins/observability/public/mock/apm.mock.ts +++ b/x-pack/plugins/observability/public/mock/apm.mock.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ApmFetchDataResponse } from '../typings/fetch_data_response'; -import { FetchData } from '../data_handler'; +import { ApmFetchDataResponse, FetchData } from '../typings'; export const fetchApmData: FetchData = () => { return Promise.resolve(response); diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index 5e0c9f99e1785..e3f373bdea1e4 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { LogsFetchDataResponse } from '../typings/fetch_data_response'; -import { FetchData } from '../data_handler'; +import { LogsFetchDataResponse, FetchData } from '../typings'; export const fetchLogsData: FetchData = () => { return new Promise((resolve) => { diff --git a/x-pack/plugins/observability/public/mock/metrics.mock.ts b/x-pack/plugins/observability/public/mock/metrics.mock.ts index 8cd1a463330e5..4d9ce187352dc 100644 --- a/x-pack/plugins/observability/public/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/mock/metrics.mock.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { MetricsFetchDataResponse } from '../typings/fetch_data_response'; -import { FetchData } from '../data_handler'; +import { MetricsFetchDataResponse, FetchData } from '../typings'; export const fetchMetricsData: FetchData = () => { return new Promise((resolve) => { diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts index 22077691898d1..7dc6477b88f93 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { UptimeFetchDataResponse } from '../typings/fetch_data_response'; -import { FetchData } from '../data_handler'; +import { UptimeFetchDataResponse, FetchData } from '../typings'; export const fetchUptimeData: FetchData = () => { return new Promise((resolve) => { diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index e65d1779520cf..38893afe29621 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -6,7 +6,7 @@ import { ObservabilityApp } from '../../../typings/common'; -interface Stat { +export interface Stat { type: 'number' | 'percent' | 'bytesPerSecond'; label: string; value: number; @@ -18,7 +18,7 @@ export interface Coordinates { y?: number; } -interface Series { +export interface Series { label: string; coordinates: Coordinates[]; color?: string; diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.test.ts b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts index aa9849bc74ef3..6643692e02dd4 100644 --- a/x-pack/plugins/observability/public/utils/format_stat_value.test.ts +++ b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { formatStatValue } from './format_stat_value'; -import { Stat } from '../typings/fetch_data_response'; +import { Stat } from '../typings'; describe('formatStatValue', () => { it('formats value as number', () => { diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.ts b/x-pack/plugins/observability/public/utils/format_stat_value.ts index 547409566f705..c200d94d5699e 100644 --- a/x-pack/plugins/observability/public/utils/format_stat_value.ts +++ b/x-pack/plugins/observability/public/utils/format_stat_value.ts @@ -5,7 +5,7 @@ */ import numeral from '@elastic/numeral'; -import { Stat } from '../typings/fetch_data_response'; +import { Stat } from '../typings'; export function formatStatValue(stat: Stat) { const { value, type } = stat; From d88e230035f150ca80ade406e4ce742400ae261f Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 11:33:22 +0200 Subject: [PATCH 67/97] adding initial story book for observability --- src/dev/storybook/aliases.ts | 1 + .../pages/dashboard/dashboard.stories.tsx | 96 +++++++++++++++++++ .../observability/scripts/storybook.js | 16 ++++ 3 files changed, 113 insertions(+) create mode 100644 x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx create mode 100644 x-pack/plugins/observability/scripts/storybook.js diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 85bfd4a7a4d26..9d9f5616b5a33 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -26,4 +26,5 @@ export const storybookAliases = { infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js', ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js', + observability: 'x-pack/plugins/observability/scripts/storybook.js', }; diff --git a/x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx b/x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx new file mode 100644 index 0000000000000..daa17e4c16f47 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { AppMountContext } from 'kibana/public'; +import { MemoryRouter } from 'react-router-dom'; +import { DashboardPage } from './'; +import { EuiThemeProvider } from '../../typings'; +import { PluginContext } from '../../context/plugin_context'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; + +const core = { + uiSettings: { + get: (key: string) => { + const euiSettings = { + [UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS]: { + from: 'now-15m', + to: 'now', + }, + [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: { + pause: true, + value: 1000, + }, + [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, + ], + }; + // @ts-expect-error + return euiSettings[key]; + }, + }, +} as AppMountContext['core']; + +storiesOf('app/Dashboard', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('Empty state', () => { + return ; + }); diff --git a/x-pack/plugins/observability/scripts/storybook.js b/x-pack/plugins/observability/scripts/storybook.js new file mode 100644 index 0000000000000..e9db98e2adf6b --- /dev/null +++ b/x-pack/plugins/observability/scripts/storybook.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'observability', + storyGlobs: [ + join(__dirname, '..', 'public', 'components', '**', '*.stories.tsx'), + join(__dirname, '..', 'public', 'pages', '**', '*.stories.tsx'), + ], +}); From 46ec7cecadc5d07c852f354390e1329b5c20d036 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 12:53:17 +0200 Subject: [PATCH 68/97] removeing uptime mock data --- x-pack/plugins/observability/public/plugin.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 101d40bffbdff..b5569911209b3 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -12,9 +12,7 @@ import { } from '../../../../src/core/public'; import { registerDataHandler } from './data_handler'; // TODO: caue: remove it later -import { fetchLogsData } from './mock/logs.mock'; import { fetchMetricsData } from './mock/metrics.mock'; -import { fetchUptimeData } from './mock/uptime.mock'; export interface ObservabilityPluginSetup { dashboard: { register: typeof registerDataHandler }; @@ -43,26 +41,11 @@ export class Plugin implements PluginClass - new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, 2000); - }), - }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, hasData: () => Promise.resolve(true), }); - registerDataHandler({ - appName: 'uptime', - fetchData: fetchUptimeData, - hasData: () => Promise.resolve(true), - }); return { dashboard: { register: registerDataHandler }, From a71003cb3e482935709b3b3650ea489a50f70868 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 13:25:31 +0200 Subject: [PATCH 69/97] fixing xDomain to show correct data on x-axis --- .../public/components/app/section/apm/index.tsx | 10 +++------- .../public/components/app/section/logs/index.tsx | 12 +++--------- .../public/components/app/section/uptime/index.tsx | 12 +++--------- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 884b6d488b55e..969b11f51c9f7 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -18,7 +18,7 @@ import { import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import d3 from 'd3'; +import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; @@ -48,12 +48,8 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { const transactionSeries = data?.series.transactions; - const xCoordinates = transactionSeries - ? transactionSeries.coordinates.map((coordinate) => coordinate.x) - : [0]; - - const min = d3.min(xCoordinates); - const max = d3.max(xCoordinates); + const min = moment.utc(startTime).valueOf(); + const max = moment.utc(endTime).valueOf(); const formatter = niceTimeFormatter([min, max]); diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 53877b9201469..142e53890697f 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -18,7 +18,7 @@ import { import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import d3 from 'd3'; +import moment from 'moment'; import React, { Fragment, useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; @@ -46,14 +46,8 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { } }, [startTime, endTime, bucketSize]); - const xCoordinates = data - ? Object.values(data.series).flatMap((serie) => - serie.coordinates.map((coordinate) => coordinate.x) - ) - : [0]; - - const min = d3.min(xCoordinates); - const max = d3.max(xCoordinates); + const min = moment.utc(startTime).valueOf(); + const max = moment.utc(endTime).valueOf(); const formatter = niceTimeFormatter([min, max]); diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 2016527c065ec..2420fdf069e9b 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -18,7 +18,7 @@ import { import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import d3 from 'd3'; +import moment from 'moment'; import React, { Fragment, useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; @@ -46,14 +46,8 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { } }, [startTime, endTime, bucketSize]); - const xCoordinates = data - ? Object.values(data.series).flatMap((serie) => - serie.coordinates.map((coordinate) => coordinate.x) - ) - : [0]; - - const min = d3.min(xCoordinates); - const max = d3.max(xCoordinates); + const min = moment.utc(startTime).valueOf(); + const max = moment.utc(endTime).valueOf(); const formatter = niceTimeFormatter([min, max]); From 62efb5f5d76c0184f6f9df266a5679d6acc740b7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 14:02:23 +0200 Subject: [PATCH 70/97] fixing empty state alignment --- .../components/app/empty_section/index.tsx | 14 +++--------- .../public/pages/dashboard/index.tsx | 22 +++++++++++-------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx index 00d5b49bfbbdc..e19bf1678bc01 100644 --- a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx @@ -3,11 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { EuiButton } from '@elastic/eui'; -import { ThemeContext } from 'styled-components'; -import { EuiText } from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import React from 'react'; import { ISection } from '../../../typings/section'; interface Props { @@ -15,14 +12,9 @@ interface Props { } export const EmptySection = ({ section }: Props) => { - const theme = useContext(ThemeContext); return ( {section.title}} diff --git a/x-pack/plugins/observability/public/pages/dashboard/index.tsx b/x-pack/plugins/observability/public/pages/dashboard/index.tsx index de0a33b9ee1d5..e91a592c68128 100644 --- a/x-pack/plugins/observability/public/pages/dashboard/index.tsx +++ b/x-pack/plugins/observability/public/pages/dashboard/index.tsx @@ -125,15 +125,19 @@ export const DashboardPage = ({ routeParams }: Props) => { - {appEmptySections - .filter(({ id }) => hasData && !hasData[id]) - .map((app) => { - return ( - - - - ); - })} + {appEmptySections.map((app) => { + return ( + + + + ); + })} )} From fe67af4ca03ade8470c709b12cc2064ce5272994 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 1 Jul 2020 18:00:51 +0200 Subject: [PATCH 71/97] adding story book and other improvements --- .../public/application/index.tsx | 16 +- .../app/section/apm/mock_data/apm.mock.ts | 11 +- .../observability/public/data_handler.ts | 4 + .../observability/public/mock/apm.mock.ts | 771 ++++++++--- .../observability/public/mock/logs.mock.ts | 29 +- .../observability/public/mock/metrics.mock.ts | 34 +- .../observability/public/mock/uptime.mock.ts | 1222 +++++++++++++++-- .../pages/dashboard/dashboard.stories.tsx | 96 -- .../observability/public/pages/home/index.tsx | 2 +- .../public/pages/landing/index.tsx | 15 - .../{dashboard => overview}/emptySection.ts | 0 .../pages/{dashboard => overview}/index.tsx | 4 +- .../pages/overview/overview.stories.tsx | 344 +++++ .../observability/public/routes/index.tsx | 28 +- 14 files changed, 2196 insertions(+), 380 deletions(-) delete mode 100644 x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx rename x-pack/plugins/observability/public/pages/{dashboard => overview}/emptySection.ts (100%) rename x-pack/plugins/observability/public/pages/{dashboard => overview}/index.tsx (98%) create mode 100644 x-pack/plugins/observability/public/pages/overview/overview.stories.tsx diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 49433b3a9cfaf..4ed2ff20b78be 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -4,14 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ import { createHashHistory } from 'history'; -import React from 'react'; +import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { PluginContext } from '../context/plugin_context'; import { useUrlParams } from '../hooks/use_url_params'; import { routes } from '../routes'; +import { usePluginContext } from '../hooks/use_plugin_context'; const App = () => { return ( @@ -21,6 +23,18 @@ const App = () => { const path = key as keyof typeof routes; const route = routes[path]; const Wrapper = () => { + const { core } = usePluginContext(); + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('xpack.observability.observability.breadcrumb.', { + defaultMessage: 'Observability', + }), + }, + ...route.breadcrumb, + ]); + }, [core]); + const { query, path: pathParams } = useUrlParams(route.params); return route.handler({ query, path: pathParams }); }; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts index affb23ea338a3..14dfc009dff91 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts +++ b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { ApmFetchDataResponse } from '../../../../../typings'; export const response: ApmFetchDataResponse = { - title: i18n.translate('apm.observabilityDashboard.title', { - defaultMessage: 'APM', - }), + title: 'APM', + appLink: '/app/apm', stats: { services: { label: 'Services', value: 11, type: 'number' }, @@ -18,9 +16,8 @@ export const response: ApmFetchDataResponse = { }, series: { transactions: { - label: i18n.translate('apm.observabilityDashboard.chart.transactions', { - defaultMessage: 'Transactions', - }), + label: 'Transactions', + color: 'euiColorVis1', coordinates: [ { x: 1591365600000, y: 32 }, diff --git a/x-pack/plugins/observability/public/data_handler.ts b/x-pack/plugins/observability/public/data_handler.ts index 6dd2cafdd2763..d7f8c471ad9aa 100644 --- a/x-pack/plugins/observability/public/data_handler.ts +++ b/x-pack/plugins/observability/public/data_handler.ts @@ -17,6 +17,10 @@ export function registerDataHandler({ dataHandlers[appName] = { fetchData, hasData }; } +export function unregisterDataHandler({ appName }: { appName: T }) { + delete dataHandlers[appName]; +} + export function getDataHandler(appName: T) { const dataHandler = dataHandlers[appName]; if (dataHandler) { diff --git a/x-pack/plugins/observability/public/mock/apm.mock.ts b/x-pack/plugins/observability/public/mock/apm.mock.ts index d5b7f6349e586..68252356428b9 100644 --- a/x-pack/plugins/observability/public/mock/apm.mock.ts +++ b/x-pack/plugins/observability/public/mock/apm.mock.ts @@ -3,8 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { i18n } from '@kbn/i18n'; import { ApmFetchDataResponse, FetchData } from '../typings'; export const fetchApmData: FetchData = () => { @@ -12,167 +10,628 @@ export const fetchApmData: FetchData = () => { }; const response: ApmFetchDataResponse = { - title: i18n.translate('apm.observabilityDashboard.title', { - defaultMessage: 'APM', - }), + title: 'APM', appLink: '/app/apm', stats: { - services: { label: 'Services', value: 11, type: 'number' }, - transactions: { label: 'Transactions', value: 312000, type: 'number', color: 'euiColorVis1' }, + services: { + type: 'number', + label: 'Services', + value: 7, + }, + transactions: { + type: 'number', + label: 'Transactions', + value: 125808, + color: '#6092c0', + }, }, series: { transactions: { - label: i18n.translate('apm.observabilityDashboard.chart.transactions', { - defaultMessage: 'Transactions', - }), - color: 'euiColorVis1', + label: 'Transactions', + color: '#6092c0', coordinates: [ - { x: 1591365600000, y: 32 }, - { x: 1591366200000, y: 43 }, - { x: 1591366800000, y: 22 }, - { x: 1591367400000, y: 29 }, - { x: 1591368000000, y: 39 }, - { x: 1591368600000, y: 36 }, - { x: 1591369200000, y: 50 }, - { x: 1591369800000, y: 31 }, - { x: 1591370400000, y: 39 }, - { x: 1591371000000, y: 26 }, - { x: 1591371600000, y: 45 }, - { x: 1591372200000, y: 27 }, - { x: 1591372800000, y: 37 }, - { x: 1591373400000, y: 55 }, - { x: 1591374000000, y: 31 }, - { x: 1591374600000, y: 26 }, - { x: 1591375200000, y: 57 }, - { x: 1591375800000, y: 25 }, - { x: 1591376400000, y: 28 }, - { x: 1591377000000, y: 40 }, - { x: 1591377600000, y: 33 }, - { x: 1591378200000, y: 33 }, - { x: 1591378800000, y: 31 }, - { x: 1591379400000, y: 32 }, - { x: 1591380000000, y: 34 }, - { x: 1591380600000, y: 31 }, - { x: 1591381200000, y: 16 }, - { x: 1591381800000, y: 34 }, - { x: 1591382400000, y: 33 }, - { x: 1591383000000, y: 35 }, - { x: 1591383600000, y: 47 }, - { x: 1591384200000, y: 44 }, - { x: 1591384800000, y: 21 }, - { x: 1591385400000, y: 25 }, - { x: 1591386000000, y: 34 }, - { x: 1591386600000, y: 37 }, - { x: 1591387200000, y: 38 }, - { x: 1591387800000, y: 28 }, - { x: 1591388400000, y: 32 }, - { x: 1591389000000, y: 37 }, - { x: 1591389600000, y: 25 }, - { x: 1591390200000, y: 33 }, - { x: 1591390800000, y: 34 }, - { x: 1591391400000, y: 30 }, - { x: 1591392000000, y: 45 }, - { x: 1591392600000, y: 42 }, - { x: 1591393200000, y: 23 }, - { x: 1591393800000, y: 33 }, - { x: 1591394400000, y: 38 }, - { x: 1591395000000, y: 30 }, - { x: 1591395600000, y: 25 }, - { x: 1591396200000, y: 33 }, - { x: 1591396800000, y: 37 }, - { x: 1591397400000, y: 43 }, - { x: 1591398000000, y: 30 }, - { x: 1591398600000, y: 36 }, - { x: 1591399200000, y: 28 }, - { x: 1591399800000, y: 39 }, - { x: 1591400400000, y: 27 }, - { x: 1591401000000, y: 41 }, - { x: 1591401600000, y: 25 }, - { x: 1591402200000, y: 31 }, - { x: 1591402800000, y: 28 }, - { x: 1591403400000, y: 29 }, - { x: 1591404000000, y: 49 }, - { x: 1591404600000, y: 24 }, - { x: 1591405200000, y: 41 }, - { x: 1591405800000, y: 30 }, - { x: 1591406400000, y: 36 }, - { x: 1591407000000, y: 39 }, - { x: 1591407600000, y: 23 }, - { x: 1591408200000, y: 40 }, - { x: 1591408800000, y: 34 }, - { x: 1591409400000, y: 28 }, - { x: 1591410000000, y: 33 }, - { x: 1591410600000, y: 31 }, - { x: 1591411200000, y: 39 }, - { x: 1591411800000, y: 33 }, - { x: 1591412400000, y: 35 }, - { x: 1591413000000, y: 31 }, - { x: 1591413600000, y: 35 }, - { x: 1591414200000, y: 37 }, - { x: 1591414800000, y: 26 }, - { x: 1591415400000, y: 27 }, - { x: 1591416000000, y: 26 }, - { x: 1591416600000, y: 34 }, - { x: 1591417200000, y: 33 }, - { x: 1591417800000, y: 38 }, - { x: 1591418400000, y: 34 }, - { x: 1591419000000, y: 37 }, - { x: 1591419600000, y: 24 }, - { x: 1591420200000, y: 25 }, - { x: 1591420800000, y: 20 }, - { x: 1591421400000, y: 35 }, - { x: 1591422000000, y: 41 }, - { x: 1591422600000, y: 40 }, - { x: 1591423200000, y: 33 }, - { x: 1591423800000, y: 24 }, - { x: 1591424400000, y: 44 }, - { x: 1591425000000, y: 24 }, - { x: 1591425600000, y: 32 }, - { x: 1591426200000, y: 37 }, - { x: 1591426800000, y: 34 }, - { x: 1591427400000, y: 28 }, - { x: 1591428000000, y: 26 }, - { x: 1591428600000, y: 37 }, - { x: 1591429200000, y: 36 }, - { x: 1591429800000, y: 37 }, - { x: 1591430400000, y: 23 }, - { x: 1591431000000, y: 47 }, - { x: 1591431600000, y: 41 }, - { x: 1591432200000, y: 24 }, - { x: 1591432800000, y: 34 }, - { x: 1591433400000, y: 27 }, - { x: 1591434000000, y: 34 }, - { x: 1591434600000, y: 44 }, - { x: 1591435200000, y: 20 }, - { x: 1591435800000, y: 34 }, - { x: 1591436400000, y: 29 }, - { x: 1591437000000, y: 28 }, - { x: 1591437600000, y: 36 }, - { x: 1591438200000, y: 34 }, - { x: 1591438800000, y: 26 }, - { x: 1591439400000, y: 29 }, - { x: 1591440000000, y: 45 }, - { x: 1591440600000, y: 34 }, - { x: 1591441200000, y: 25 }, - { x: 1591441800000, y: 34 }, - { x: 1591442400000, y: 28 }, - { x: 1591443000000, y: 34 }, - { x: 1591443600000, y: 31 }, - { x: 1591444200000, y: 24 }, - { x: 1591444800000, y: 34 }, - { x: 1591445400000, y: 21 }, - { x: 1591446000000, y: 40 }, - { x: 1591446600000, y: 37 }, - { x: 1591447200000, y: 31 }, - { x: 1591447800000, y: 21 }, - { x: 1591448400000, y: 24 }, - { x: 1591449000000, y: 30 }, - { x: 1591449600000, y: 22 }, - { x: 1591450200000, y: 27 }, - { x: 1591450800000, y: 30 }, - { x: 1591451400000, y: 22 }, - { x: 1591452000000, y: 9 }, + { + x: 1593295200000, + y: 891, + }, + { + x: 1593297000000, + y: 902, + }, + { + x: 1593298800000, + y: 924, + }, + { + x: 1593300600000, + y: 944, + }, + { + x: 1593302400000, + y: 935, + }, + { + x: 1593304200000, + y: 915, + }, + { + x: 1593306000000, + y: 917, + }, + { + x: 1593307800000, + y: 941, + }, + { + x: 1593309600000, + y: 906, + }, + { + x: 1593311400000, + y: 939, + }, + { + x: 1593313200000, + y: 961, + }, + { + x: 1593315000000, + y: 911, + }, + { + x: 1593316800000, + y: 958, + }, + { + x: 1593318600000, + y: 861, + }, + { + x: 1593320400000, + y: 906, + }, + { + x: 1593322200000, + y: 899, + }, + { + x: 1593324000000, + y: 785, + }, + { + x: 1593325800000, + y: 952, + }, + { + x: 1593327600000, + y: 910, + }, + { + x: 1593329400000, + y: 869, + }, + { + x: 1593331200000, + y: 895, + }, + { + x: 1593333000000, + y: 924, + }, + { + x: 1593334800000, + y: 930, + }, + { + x: 1593336600000, + y: 947, + }, + { + x: 1593338400000, + y: 905, + }, + { + x: 1593340200000, + y: 963, + }, + { + x: 1593342000000, + y: 877, + }, + { + x: 1593343800000, + y: 839, + }, + { + x: 1593345600000, + y: 884, + }, + { + x: 1593347400000, + y: 934, + }, + { + x: 1593349200000, + y: 908, + }, + { + x: 1593351000000, + y: 982, + }, + { + x: 1593352800000, + y: 897, + }, + { + x: 1593354600000, + y: 903, + }, + { + x: 1593356400000, + y: 877, + }, + { + x: 1593358200000, + y: 893, + }, + { + x: 1593360000000, + y: 919, + }, + { + x: 1593361800000, + y: 844, + }, + { + x: 1593363600000, + y: 940, + }, + { + x: 1593365400000, + y: 951, + }, + { + x: 1593367200000, + y: 869, + }, + { + x: 1593369000000, + y: 901, + }, + { + x: 1593370800000, + y: 940, + }, + { + x: 1593372600000, + y: 942, + }, + { + x: 1593374400000, + y: 881, + }, + { + x: 1593376200000, + y: 935, + }, + { + x: 1593378000000, + y: 892, + }, + { + x: 1593379800000, + y: 861, + }, + { + x: 1593381600000, + y: 868, + }, + { + x: 1593383400000, + y: 990, + }, + { + x: 1593385200000, + y: 931, + }, + { + x: 1593387000000, + y: 898, + }, + { + x: 1593388800000, + y: 906, + }, + { + x: 1593390600000, + y: 928, + }, + { + x: 1593392400000, + y: 975, + }, + { + x: 1593394200000, + y: 842, + }, + { + x: 1593396000000, + y: 940, + }, + { + x: 1593397800000, + y: 922, + }, + { + x: 1593399600000, + y: 962, + }, + { + x: 1593401400000, + y: 940, + }, + { + x: 1593403200000, + y: 974, + }, + { + x: 1593405000000, + y: 887, + }, + { + x: 1593406800000, + y: 920, + }, + { + x: 1593408600000, + y: 854, + }, + { + x: 1593410400000, + y: 898, + }, + { + x: 1593412200000, + y: 952, + }, + { + x: 1593414000000, + y: 987, + }, + { + x: 1593415800000, + y: 932, + }, + { + x: 1593417600000, + y: 1009, + }, + { + x: 1593419400000, + y: 989, + }, + { + x: 1593421200000, + y: 939, + }, + { + x: 1593423000000, + y: 929, + }, + { + x: 1593424800000, + y: 929, + }, + { + x: 1593426600000, + y: 864, + }, + { + x: 1593428400000, + y: 895, + }, + { + x: 1593430200000, + y: 876, + }, + { + x: 1593432000000, + y: 68, + }, + { + x: 1593433800000, + y: 0, + }, + { + x: 1593435600000, + y: 0, + }, + { + x: 1593437400000, + y: 0, + }, + { + x: 1593439200000, + y: 0, + }, + { + x: 1593441000000, + y: 0, + }, + { + x: 1593442800000, + y: 700, + }, + { + x: 1593444600000, + y: 930, + }, + { + x: 1593446400000, + y: 953, + }, + { + x: 1593448200000, + y: 995, + }, + { + x: 1593450000000, + y: 883, + }, + { + x: 1593451800000, + y: 902, + }, + { + x: 1593453600000, + y: 988, + }, + { + x: 1593455400000, + y: 947, + }, + { + x: 1593457200000, + y: 889, + }, + { + x: 1593459000000, + y: 982, + }, + { + x: 1593460800000, + y: 919, + }, + { + x: 1593462600000, + y: 854, + }, + { + x: 1593464400000, + y: 894, + }, + { + x: 1593466200000, + y: 901, + }, + { + x: 1593468000000, + y: 970, + }, + { + x: 1593469800000, + y: 840, + }, + { + x: 1593471600000, + y: 857, + }, + { + x: 1593473400000, + y: 943, + }, + { + x: 1593475200000, + y: 825, + }, + { + x: 1593477000000, + y: 955, + }, + { + x: 1593478800000, + y: 959, + }, + { + x: 1593480600000, + y: 921, + }, + { + x: 1593482400000, + y: 924, + }, + { + x: 1593484200000, + y: 840, + }, + { + x: 1593486000000, + y: 943, + }, + { + x: 1593487800000, + y: 919, + }, + { + x: 1593489600000, + y: 882, + }, + { + x: 1593491400000, + y: 900, + }, + { + x: 1593493200000, + y: 930, + }, + { + x: 1593495000000, + y: 854, + }, + { + x: 1593496800000, + y: 905, + }, + { + x: 1593498600000, + y: 922, + }, + { + x: 1593500400000, + y: 863, + }, + { + x: 1593502200000, + y: 966, + }, + { + x: 1593504000000, + y: 910, + }, + { + x: 1593505800000, + y: 851, + }, + { + x: 1593507600000, + y: 867, + }, + { + x: 1593509400000, + y: 904, + }, + { + x: 1593511200000, + y: 913, + }, + { + x: 1593513000000, + y: 889, + }, + { + x: 1593514800000, + y: 907, + }, + { + x: 1593516600000, + y: 965, + }, + { + x: 1593518400000, + y: 868, + }, + { + x: 1593520200000, + y: 919, + }, + { + x: 1593522000000, + y: 945, + }, + { + x: 1593523800000, + y: 883, + }, + { + x: 1593525600000, + y: 902, + }, + { + x: 1593527400000, + y: 900, + }, + { + x: 1593529200000, + y: 829, + }, + { + x: 1593531000000, + y: 919, + }, + { + x: 1593532800000, + y: 942, + }, + { + x: 1593534600000, + y: 924, + }, + { + x: 1593536400000, + y: 958, + }, + { + x: 1593538200000, + y: 867, + }, + { + x: 1593540000000, + y: 844, + }, + { + x: 1593541800000, + y: 976, + }, + { + x: 1593543600000, + y: 937, + }, + { + x: 1593545400000, + y: 891, + }, + { + x: 1593547200000, + y: 936, + }, + { + x: 1593549000000, + y: 895, + }, + { + x: 1593550800000, + y: 850, + }, + { + x: 1593552600000, + y: 899, + }, ], }, }, }; + +export const emptyResponse: ApmFetchDataResponse = { + title: 'APM', + appLink: '/app/apm', + stats: { + services: { + type: 'number', + label: 'Services', + value: 0, + }, + transactions: { + type: 'number', + label: 'Transactions', + value: 0, + color: '#6092c0', + }, + }, + series: { + transactions: { + label: 'Transactions', + color: '#6092c0', + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts index e3f373bdea1e4..05e2d4aa76b9e 100644 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ b/x-pack/plugins/observability/public/mock/logs.mock.ts @@ -4,21 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; -import { LogsFetchDataResponse, FetchData } from '../typings'; +import { FetchData, LogsFetchDataResponse } from '../typings'; export const fetchLogsData: FetchData = () => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(response); - }, 1000); - }); + return Promise.resolve(response); }; const response: LogsFetchDataResponse = { - title: i18n.translate('xpack.logs.observabilityDashboard.title', { - defaultMessage: 'Logs', - }), + title: 'Logs', appLink: '/app/logs', stats: { unknown: { label: 'Unknown', value: 73777, type: 'number' }, @@ -151,3 +144,19 @@ const response: LogsFetchDataResponse = { }, }, }; + +export const emptyResponse: LogsFetchDataResponse = { + title: 'Logs', + appLink: '/app/logs', + stats: { + unknown: { label: 'Unknown', value: 0, type: 'number' }, + 'kibana.log': { label: 'Kibana.log', value: 0, type: 'number' }, + 'nginx.access': { label: 'Nginx.access', value: 0, type: 'number' }, + }, + series: { + unknown: { + label: 'Unknwon', + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/mock/metrics.mock.ts b/x-pack/plugins/observability/public/mock/metrics.mock.ts index 4d9ce187352dc..4ca1380839096 100644 --- a/x-pack/plugins/observability/public/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/mock/metrics.mock.ts @@ -4,21 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { MetricsFetchDataResponse, FetchData } from '../typings'; export const fetchMetricsData: FetchData = () => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(response); - }, 1500); - }); + return Promise.resolve(response); }; const response: MetricsFetchDataResponse = { - title: i18n.translate('metrics.observabilityDashboard.title', { - defaultMessage: 'Metrics', - }), + title: 'Metrics', appLink: '/app/apm', stats: { hosts: { label: 'Hosts', value: 11, type: 'number' }, @@ -121,3 +114,26 @@ const response: MetricsFetchDataResponse = { }, }, }; + +export const emptyResponse: MetricsFetchDataResponse = { + title: 'Metrics', + appLink: '/app/apm', + stats: { + hosts: { label: 'Hosts', value: 0, type: 'number' }, + cpu: { label: 'CPU usage', value: 0, type: 'percent' }, + memory: { label: 'Memory Usage', value: 0, type: 'percent' }, + disk: { label: 'Disk Usage', value: 0, type: 'percent' }, + inboundTraffic: { label: 'Inbount traffic', value: 0, type: 'bytesPerSecond' }, + outboundTraffic: { label: 'Outbount traffic', value: 0, type: 'bytesPerSecond' }, + }, + series: { + outboundTraffic: { + label: 'Outbount traffic', + coordinates: [], + }, + inboundTraffic: { + label: 'Inbound traffic', + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/mock/uptime.mock.ts index 7dc6477b88f93..b57d8ccf8f5c6 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/mock/uptime.mock.ts @@ -6,161 +6,1223 @@ import { UptimeFetchDataResponse, FetchData } from '../typings'; export const fetchUptimeData: FetchData = () => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(response); - }, 3000); - }); + return Promise.resolve(response); }; const response: UptimeFetchDataResponse = { title: 'Uptime', - appLink: '/app/uptime', + appLink: '/app/uptime#/', stats: { - monitors: { label: 'Monitors', value: 5, type: 'number' }, - down: { label: 'Down', value: 115, type: 'number' }, - up: { label: 'Up', value: 582, type: 'number' }, + monitors: { + type: 'number', + label: 'Monitors', + value: 26, + }, + up: { + type: 'number', + label: 'Up', + value: 20, + }, + down: { + type: 'number', + label: 'Down', + value: 6, + }, }, series: { + up: { + label: 'Up', + coordinates: [ + { + x: 1593295200000, + y: 1170, + }, + { + x: 1593297000000, + y: 1170, + }, + { + x: 1593298800000, + y: 1170, + }, + { + x: 1593300600000, + y: 1170, + }, + { + x: 1593302400000, + y: 1170, + }, + { + x: 1593304200000, + y: 1170, + }, + { + x: 1593306000000, + y: 1170, + }, + { + x: 1593307800000, + y: 1170, + }, + { + x: 1593309600000, + y: 1170, + }, + { + x: 1593311400000, + y: 1170, + }, + { + x: 1593313200000, + y: 1170, + }, + { + x: 1593315000000, + y: 1170, + }, + { + x: 1593316800000, + y: 1170, + }, + { + x: 1593318600000, + y: 1170, + }, + { + x: 1593320400000, + y: 1170, + }, + { + x: 1593322200000, + y: 1170, + }, + { + x: 1593324000000, + y: 1170, + }, + { + x: 1593325800000, + y: 1170, + }, + { + x: 1593327600000, + y: 1170, + }, + { + x: 1593329400000, + y: 1170, + }, + { + x: 1593331200000, + y: 1170, + }, + { + x: 1593333000000, + y: 1170, + }, + { + x: 1593334800000, + y: 1170, + }, + { + x: 1593336600000, + y: 1170, + }, + { + x: 1593338400000, + y: 1170, + }, + { + x: 1593340200000, + y: 1170, + }, + { + x: 1593342000000, + y: 1170, + }, + { + x: 1593343800000, + y: 1170, + }, + { + x: 1593345600000, + y: 1170, + }, + { + x: 1593347400000, + y: 1170, + }, + { + x: 1593349200000, + y: 1170, + }, + { + x: 1593351000000, + y: 1170, + }, + { + x: 1593352800000, + y: 1170, + }, + { + x: 1593354600000, + y: 1170, + }, + { + x: 1593356400000, + y: 1170, + }, + { + x: 1593358200000, + y: 1170, + }, + { + x: 1593360000000, + y: 1170, + }, + { + x: 1593361800000, + y: 1170, + }, + { + x: 1593363600000, + y: 1170, + }, + { + x: 1593365400000, + y: 1170, + }, + { + x: 1593367200000, + y: 1170, + }, + { + x: 1593369000000, + y: 1170, + }, + { + x: 1593370800000, + y: 1170, + }, + { + x: 1593372600000, + y: 1170, + }, + { + x: 1593374400000, + y: 1169, + }, + { + x: 1593376200000, + y: 1170, + }, + { + x: 1593378000000, + y: 1170, + }, + { + x: 1593379800000, + y: 1170, + }, + { + x: 1593381600000, + y: 1170, + }, + { + x: 1593383400000, + y: 1170, + }, + { + x: 1593385200000, + y: 1170, + }, + { + x: 1593387000000, + y: 1170, + }, + { + x: 1593388800000, + y: 1170, + }, + { + x: 1593390600000, + y: 1170, + }, + { + x: 1593392400000, + y: 1170, + }, + { + x: 1593394200000, + y: 1239, + }, + { + x: 1593396000000, + y: 1170, + }, + { + x: 1593397800000, + y: 1170, + }, + { + x: 1593399600000, + y: 1170, + }, + { + x: 1593401400000, + y: 1170, + }, + { + x: 1593403200000, + y: 1170, + }, + { + x: 1593405000000, + y: 1170, + }, + { + x: 1593406800000, + y: 1170, + }, + { + x: 1593408600000, + y: 1170, + }, + { + x: 1593410400000, + y: 1170, + }, + { + x: 1593412200000, + y: 1170, + }, + { + x: 1593414000000, + y: 1170, + }, + { + x: 1593415800000, + y: 1170, + }, + { + x: 1593417600000, + y: 1170, + }, + { + x: 1593419400000, + y: 1170, + }, + { + x: 1593421200000, + y: 1170, + }, + { + x: 1593423000000, + y: 1170, + }, + { + x: 1593424800000, + y: 1166, + }, + { + x: 1593426600000, + y: 1206, + }, + { + x: 1593428400000, + y: 1143, + }, + { + x: 1593430200000, + y: 1170, + }, + { + x: 1593432000000, + y: 1170, + }, + { + x: 1593433800000, + y: 1170, + }, + { + x: 1593435600000, + y: 1170, + }, + { + x: 1593437400000, + y: 1170, + }, + { + x: 1593439200000, + y: 1170, + }, + { + x: 1593441000000, + y: 1170, + }, + { + x: 1593442800000, + y: 1170, + }, + { + x: 1593444600000, + y: 1170, + }, + { + x: 1593446400000, + y: 1170, + }, + { + x: 1593448200000, + y: 1170, + }, + { + x: 1593450000000, + y: 1170, + }, + { + x: 1593451800000, + y: 1170, + }, + { + x: 1593453600000, + y: 1170, + }, + { + x: 1593455400000, + y: 1170, + }, + { + x: 1593457200000, + y: 1170, + }, + { + x: 1593459000000, + y: 1170, + }, + { + x: 1593460800000, + y: 1170, + }, + { + x: 1593462600000, + y: 1170, + }, + { + x: 1593464400000, + y: 1170, + }, + { + x: 1593466200000, + y: 1170, + }, + { + x: 1593468000000, + y: 1170, + }, + { + x: 1593469800000, + y: 1170, + }, + { + x: 1593471600000, + y: 1170, + }, + { + x: 1593473400000, + y: 1170, + }, + { + x: 1593475200000, + y: 1170, + }, + { + x: 1593477000000, + y: 1170, + }, + { + x: 1593478800000, + y: 1170, + }, + { + x: 1593480600000, + y: 1201, + }, + { + x: 1593482400000, + y: 1139, + }, + { + x: 1593484200000, + y: 1140, + }, + { + x: 1593486000000, + y: 1140, + }, + { + x: 1593487800000, + y: 1140, + }, + { + x: 1593489600000, + y: 1140, + }, + { + x: 1593491400000, + y: 1140, + }, + { + x: 1593493200000, + y: 1140, + }, + { + x: 1593495000000, + y: 1140, + }, + { + x: 1593496800000, + y: 1140, + }, + { + x: 1593498600000, + y: 1140, + }, + { + x: 1593500400000, + y: 1140, + }, + { + x: 1593502200000, + y: 1140, + }, + { + x: 1593504000000, + y: 1140, + }, + { + x: 1593505800000, + y: 1140, + }, + { + x: 1593507600000, + y: 1140, + }, + { + x: 1593509400000, + y: 1140, + }, + { + x: 1593511200000, + y: 1140, + }, + { + x: 1593513000000, + y: 1140, + }, + { + x: 1593514800000, + y: 1140, + }, + { + x: 1593516600000, + y: 1140, + }, + { + x: 1593518400000, + y: 1140, + }, + { + x: 1593520200000, + y: 1140, + }, + { + x: 1593522000000, + y: 1140, + }, + { + x: 1593523800000, + y: 1140, + }, + { + x: 1593525600000, + y: 1140, + }, + { + x: 1593527400000, + y: 1140, + }, + { + x: 1593529200000, + y: 1140, + }, + { + x: 1593531000000, + y: 1140, + }, + { + x: 1593532800000, + y: 1140, + }, + { + x: 1593534600000, + y: 1140, + }, + { + x: 1593536400000, + y: 1140, + }, + { + x: 1593538200000, + y: 1140, + }, + { + x: 1593540000000, + y: 1140, + }, + { + x: 1593541800000, + y: 1139, + }, + { + x: 1593543600000, + y: 1140, + }, + { + x: 1593545400000, + y: 1140, + }, + { + x: 1593547200000, + y: 1140, + }, + { + x: 1593549000000, + y: 1140, + }, + { + x: 1593550800000, + y: 1140, + }, + { + x: 1593552600000, + y: 1140, + }, + ], + }, down: { label: 'Down', - color: 'euiColorVis2', coordinates: [ { - x: 1591439940000, - y: 2, + x: 1593295200000, + y: 234, }, { - x: 1591440000000, - y: 6, + x: 1593297000000, + y: 234, }, { - x: 1591440060000, - y: 6, + x: 1593298800000, + y: 234, }, { - x: 1591440120000, - y: 6, + x: 1593300600000, + y: 234, }, { - x: 1591440180000, - y: 15, + x: 1593302400000, + y: 234, }, { - x: 1591440240000, - y: 6, + x: 1593304200000, + y: 234, }, { - x: 1591440300000, - y: 6, + x: 1593306000000, + y: 234, }, { - x: 1591440360000, - y: 6, + x: 1593307800000, + y: 234, }, { - x: 1591440420000, - y: 6, + x: 1593309600000, + y: 234, }, { - x: 1591440480000, - y: 15, + x: 1593311400000, + y: 234, }, { - x: 1591440540000, - y: 6, + x: 1593313200000, + y: 234, }, { - x: 1591440600000, - y: 6, + x: 1593315000000, + y: 234, }, { - x: 1591440660000, - y: 6, + x: 1593316800000, + y: 234, }, { - x: 1591440720000, - y: 6, + x: 1593318600000, + y: 234, }, { - x: 1591440780000, - y: 15, + x: 1593320400000, + y: 234, }, { - x: 1591440840000, - y: 2, + x: 1593322200000, + y: 234, + }, + { + x: 1593324000000, + y: 234, + }, + { + x: 1593325800000, + y: 234, + }, + { + x: 1593327600000, + y: 234, + }, + { + x: 1593329400000, + y: 234, + }, + { + x: 1593331200000, + y: 234, + }, + { + x: 1593333000000, + y: 234, + }, + { + x: 1593334800000, + y: 234, + }, + { + x: 1593336600000, + y: 234, + }, + { + x: 1593338400000, + y: 234, + }, + { + x: 1593340200000, + y: 234, + }, + { + x: 1593342000000, + y: 234, + }, + { + x: 1593343800000, + y: 234, + }, + { + x: 1593345600000, + y: 234, + }, + { + x: 1593347400000, + y: 234, + }, + { + x: 1593349200000, + y: 234, + }, + { + x: 1593351000000, + y: 234, + }, + { + x: 1593352800000, + y: 234, + }, + { + x: 1593354600000, + y: 234, + }, + { + x: 1593356400000, + y: 234, + }, + { + x: 1593358200000, + y: 234, + }, + { + x: 1593360000000, + y: 234, + }, + { + x: 1593361800000, + y: 234, + }, + { + x: 1593363600000, + y: 234, + }, + { + x: 1593365400000, + y: 234, + }, + { + x: 1593367200000, + y: 234, + }, + { + x: 1593369000000, + y: 234, + }, + { + x: 1593370800000, + y: 234, + }, + { + x: 1593372600000, + y: 234, + }, + { + x: 1593374400000, + y: 235, + }, + { + x: 1593376200000, + y: 234, + }, + { + x: 1593378000000, + y: 234, + }, + { + x: 1593379800000, + y: 234, + }, + { + x: 1593381600000, + y: 234, + }, + { + x: 1593383400000, + y: 234, + }, + { + x: 1593385200000, + y: 234, + }, + { + x: 1593387000000, + y: 234, + }, + { + x: 1593388800000, + y: 234, + }, + { + x: 1593390600000, + y: 234, + }, + { + x: 1593392400000, + y: 234, + }, + { + x: 1593394200000, + y: 246, + }, + { + x: 1593396000000, + y: 234, + }, + { + x: 1593397800000, + y: 234, + }, + { + x: 1593399600000, + y: 234, + }, + { + x: 1593401400000, + y: 234, + }, + { + x: 1593403200000, + y: 234, + }, + { + x: 1593405000000, + y: 234, + }, + { + x: 1593406800000, + y: 234, + }, + { + x: 1593408600000, + y: 234, + }, + { + x: 1593410400000, + y: 234, + }, + { + x: 1593412200000, + y: 234, + }, + { + x: 1593414000000, + y: 234, + }, + { + x: 1593415800000, + y: 234, + }, + { + x: 1593417600000, + y: 234, + }, + { + x: 1593419400000, + y: 234, + }, + { + x: 1593421200000, + y: 234, + }, + { + x: 1593423000000, + y: 234, + }, + { + x: 1593424800000, + y: 240, + }, + { + x: 1593426600000, + y: 254, }, - ], - }, - up: { - label: 'Up', - color: 'euiColorLightShade', - coordinates: [ { - x: 1591439940000, - y: 12, + x: 1593428400000, + y: 231, }, { - x: 1591440000000, - y: 30, + x: 1593430200000, + y: 234, }, { - x: 1591440060000, - y: 30, + x: 1593432000000, + y: 234, }, { - x: 1591440120000, - y: 30, + x: 1593433800000, + y: 234, }, { - x: 1591440180000, - y: 75, + x: 1593435600000, + y: 234, }, { - x: 1591440240000, - y: 30, + x: 1593437400000, + y: 234, }, { - x: 1591440300000, - y: 30, + x: 1593439200000, + y: 234, }, { - x: 1591440360000, - y: 30, + x: 1593441000000, + y: 234, }, { - x: 1591440420000, - y: 30, + x: 1593442800000, + y: 234, }, { - x: 1591440480000, - y: 75, + x: 1593444600000, + y: 234, }, { - x: 1591440540000, - y: 30, + x: 1593446400000, + y: 234, }, { - x: 1591440600000, - y: 30, + x: 1593448200000, + y: 234, }, { - x: 1591440660000, - y: 30, + x: 1593450000000, + y: 234, }, { - x: 1591440720000, - y: 30, + x: 1593451800000, + y: 234, }, { - x: 1591440780000, - y: 75, + x: 1593453600000, + y: 234, }, { - x: 1591440840000, - y: 15, + x: 1593455400000, + y: 234, + }, + { + x: 1593457200000, + y: 234, + }, + { + x: 1593459000000, + y: 234, + }, + { + x: 1593460800000, + y: 234, + }, + { + x: 1593462600000, + y: 234, + }, + { + x: 1593464400000, + y: 234, + }, + { + x: 1593466200000, + y: 234, + }, + { + x: 1593468000000, + y: 234, + }, + { + x: 1593469800000, + y: 234, + }, + { + x: 1593471600000, + y: 234, + }, + { + x: 1593473400000, + y: 234, + }, + { + x: 1593475200000, + y: 234, + }, + { + x: 1593477000000, + y: 234, + }, + { + x: 1593478800000, + y: 234, + }, + { + x: 1593480600000, + y: 254, + }, + { + x: 1593482400000, + y: 265, + }, + { + x: 1593484200000, + y: 264, + }, + { + x: 1593486000000, + y: 264, + }, + { + x: 1593487800000, + y: 264, + }, + { + x: 1593489600000, + y: 264, + }, + { + x: 1593491400000, + y: 264, + }, + { + x: 1593493200000, + y: 264, + }, + { + x: 1593495000000, + y: 264, + }, + { + x: 1593496800000, + y: 264, + }, + { + x: 1593498600000, + y: 264, + }, + { + x: 1593500400000, + y: 264, + }, + { + x: 1593502200000, + y: 264, + }, + { + x: 1593504000000, + y: 264, + }, + { + x: 1593505800000, + y: 264, + }, + { + x: 1593507600000, + y: 264, + }, + { + x: 1593509400000, + y: 264, + }, + { + x: 1593511200000, + y: 264, + }, + { + x: 1593513000000, + y: 264, + }, + { + x: 1593514800000, + y: 264, + }, + { + x: 1593516600000, + y: 264, + }, + { + x: 1593518400000, + y: 264, + }, + { + x: 1593520200000, + y: 264, + }, + { + x: 1593522000000, + y: 264, + }, + { + x: 1593523800000, + y: 264, + }, + { + x: 1593525600000, + y: 264, + }, + { + x: 1593527400000, + y: 264, + }, + { + x: 1593529200000, + y: 264, + }, + { + x: 1593531000000, + y: 264, + }, + { + x: 1593532800000, + y: 264, + }, + { + x: 1593534600000, + y: 264, + }, + { + x: 1593536400000, + y: 264, + }, + { + x: 1593538200000, + y: 264, + }, + { + x: 1593540000000, + y: 264, + }, + { + x: 1593541800000, + y: 265, + }, + { + x: 1593543600000, + y: 264, + }, + { + x: 1593545400000, + y: 264, + }, + { + x: 1593547200000, + y: 264, + }, + { + x: 1593549000000, + y: 264, + }, + { + x: 1593550800000, + y: 264, + }, + { + x: 1593552600000, + y: 264, }, ], }, }, }; + +export const emptyResponse: UptimeFetchDataResponse = { + title: 'Uptime', + appLink: '/app/uptime#/', + stats: { + monitors: { + type: 'number', + label: 'Monitors', + value: 0, + }, + up: { + type: 'number', + label: 'Up', + value: 0, + }, + down: { + type: 'number', + label: 'Down', + value: 0, + }, + }, + series: { + up: { + label: 'Up', + coordinates: [], + }, + down: { + label: 'Down', + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx b/x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx deleted file mode 100644 index daa17e4c16f47..0000000000000 --- a/x-pack/plugins/observability/public/pages/dashboard/dashboard.stories.tsx +++ /dev/null @@ -1,96 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { storiesOf } from '@storybook/react'; -import React from 'react'; -import { AppMountContext } from 'kibana/public'; -import { MemoryRouter } from 'react-router-dom'; -import { DashboardPage } from './'; -import { EuiThemeProvider } from '../../typings'; -import { PluginContext } from '../../context/plugin_context'; -import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; - -const core = { - uiSettings: { - get: (key: string) => { - const euiSettings = { - [UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS]: { - from: 'now-15m', - to: 'now', - }, - [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: { - pause: true, - value: 1000, - }, - [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [ - { - from: 'now/d', - to: 'now/d', - display: 'Today', - }, - { - from: 'now/w', - to: 'now/w', - display: 'This week', - }, - { - from: 'now-15m', - to: 'now', - display: 'Last 15 minutes', - }, - { - from: 'now-30m', - to: 'now', - display: 'Last 30 minutes', - }, - { - from: 'now-1h', - to: 'now', - display: 'Last 1 hour', - }, - { - from: 'now-24h', - to: 'now', - display: 'Last 24 hours', - }, - { - from: 'now-7d', - to: 'now', - display: 'Last 7 days', - }, - { - from: 'now-30d', - to: 'now', - display: 'Last 30 days', - }, - { - from: 'now-90d', - to: 'now', - display: 'Last 90 days', - }, - { - from: 'now-1y', - to: 'now', - display: 'Last 1 year', - }, - ], - }; - // @ts-expect-error - return euiSettings[key]; - }, - }, -} as AppMountContext['core']; - -storiesOf('app/Dashboard', module) - .addDecorator((storyFn) => ( - - - {storyFn()}) - - - )) - .add('Empty state', () => { - return ; - }); diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 14d8bd57de41f..59513fc047f17 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -16,7 +16,7 @@ export const HomePage = () => { const hasSomeData = values.length ? values.some((hasData) => hasData) : null; if (hasSomeData === true) { - history.push({ pathname: '/dashboard' }); + history.push({ pathname: '/overview' }); } if (hasSomeData === false) { history.push({ pathname: '/landing' }); diff --git a/x-pack/plugins/observability/public/pages/landing/index.tsx b/x-pack/plugins/observability/public/pages/landing/index.tsx index a256bb844c8a9..711496a625e7a 100644 --- a/x-pack/plugins/observability/public/pages/landing/index.tsx +++ b/x-pack/plugins/observability/public/pages/landing/index.tsx @@ -31,21 +31,6 @@ export const LandingPage = () => { const { core } = usePluginContext(); const theme = useContext(ThemeContext); - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('xpack.observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, [core]); - return ( ; + routeParams: RouteParams<'/overview'>; } -export const DashboardPage = ({ routeParams }: Props) => { +export const OverviewPage = ({ routeParams }: Props) => { const result = useFetcher(() => fetchHasData(), []); const hasData = result.data; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx new file mode 100644 index 0000000000000..b8eb6c76dc63e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -0,0 +1,344 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import { AppMountContext } from 'kibana/public'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; +import { PluginContext } from '../../context/plugin_context'; +import { registerDataHandler, unregisterDataHandler } from '../../data_handler'; +import { emptyResponse as emptyAPMResponse, fetchApmData } from '../../mock/apm.mock'; +import { fetchLogsData, emptyResponse as emptyLogsResponse } from '../../mock/logs.mock'; +import { fetchMetricsData, emptyResponse as emptyMetricsResponse } from '../../mock/metrics.mock'; +import { fetchUptimeData, emptyResponse as emptyUptimeResponse } from '../../mock/uptime.mock'; +import { EuiThemeProvider } from '../../typings'; +import { OverviewPage } from './'; + +const core = { + uiSettings: { + get: (key: string) => { + const euiSettings = { + [UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS]: { + from: 'now-15m', + to: 'now', + }, + [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: { + pause: true, + value: 1000, + }, + [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, + ], + }; + // @ts-expect-error + return euiSettings[key]; + }, + }, +} as AppMountContext['core']; + +function unregisterAll() { + unregisterDataHandler({ appName: 'apm' }); + unregisterDataHandler({ appName: 'infra_logs' }); + unregisterDataHandler({ appName: 'infra_metrics' }); + unregisterDataHandler({ appName: 'uptime' }); +} + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('Empty state', () => { + unregisterAll(); + return ; + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('single panel', () => { + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs and metrics', () => { + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics and alerts', () => { + // TODO: add alert here + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics, APM and alerts', () => { + // TODO: add alert here + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics, APM and Uptime', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics, APM, Uptime and Alerts', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('no data', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: async () => emptyAPMResponse, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: async () => emptyLogsResponse, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: async () => emptyMetricsResponse, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'uptime', + fetchData: async () => emptyUptimeResponse, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 26c2c95d3edd2..10f9b4dc42723 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -5,9 +5,10 @@ */ import React from 'react'; import * as t from 'io-ts'; +import { i18n } from '@kbn/i18n'; import { HomePage } from '../pages/home'; import { LandingPage } from '../pages/landing'; -import { DashboardPage } from '../pages/dashboard'; +import { OverviewPage } from '../pages/overview'; import { jsonRt } from './json_rt'; export type RouteParams = DecodeParams; @@ -26,16 +27,30 @@ export const routes = { return ; }, params: {}, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.home.breadcrumb', { + defaultMessage: 'Overview', + }), + }, + ], }, '/landing': { handler: () => { return ; }, params: {}, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.landing.breadcrumb', { + defaultMessage: 'Getting started', + }), + }, + ], }, - '/dashboard': { + '/overview': { handler: ({ query }: any) => { - return ; + return ; }, params: { query: t.partial({ @@ -45,5 +60,12 @@ export const routes = { refreshInterval: jsonRt.pipe(t.number), }), }, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.overview.breadcrumb', { + defaultMessage: 'Overview', + }), + }, + ], }, }; From ff47c400bd4063952d6837efb97183bf7b328ba5 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 2 Jul 2020 13:41:14 +0200 Subject: [PATCH 72/97] adding news component --- .../public/components/app/news/index.scss | 3 + .../public/components/app/news/index.test.tsx | 21 ++++ .../public/components/app/news/index.tsx | 97 +++++++++++++++++++ .../app/news/mock/news.mock.data.ts | 34 +++++++ .../components/app/resources/index.test.tsx | 12 --- .../public/components/app/resources/index.tsx | 33 ++----- .../public/pages/overview/index.tsx | 12 ++- 7 files changed, 175 insertions(+), 37 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/news/index.scss create mode 100644 x-pack/plugins/observability/public/components/app/news/index.test.tsx create mode 100644 x-pack/plugins/observability/public/components/app/news/index.tsx create mode 100644 x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts diff --git a/x-pack/plugins/observability/public/components/app/news/index.scss b/x-pack/plugins/observability/public/components/app/news/index.scss new file mode 100644 index 0000000000000..d1a7eba94be67 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/index.scss @@ -0,0 +1,3 @@ +.newItem__image{ + @include euiBottomShadowSmall; +} \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/news/index.test.tsx b/x-pack/plugins/observability/public/components/app/news/index.test.tsx new file mode 100644 index 0000000000000..1ed9212f6c6c0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/index.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { render } from '@testing-library/react'; +import React from 'react'; +import { News } from './'; +import { EuiThemeProvider } from '../../../typings'; + +describe('News', () => { + it('renders resources with all elements', () => { + const { getByText, getAllByText } = render( + + + + ); + expect(getByText("What's new")).toBeInTheDocument(); + expect(getAllByText('Read full story')).not.toEqual([]); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx new file mode 100644 index 0000000000000..8930a1c5173d2 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; +import './index.scss'; +import { news as newsMockData } from './mock/news.mock.data'; + +interface News { + title: string; + description: string; + link_url: string; + image_url: string; +} + +export const News = () => { + const news: News[] = newsMockData; + return ( + + + +

+ {i18n.translate('xpack.observability.news.title', { + defaultMessage: "What's new", + })} +

+
+
+ {news.map((item, index) => ( + + + + ))} +
+ ); +}; + +const limitString = (string: string, limit: number) => + `${string.slice(0, limit)}${string.length > limit ? '...' : ''}`; + +const NewsItem = ({ item }: { item: News }) => { + const theme = useContext(ThemeContext); + + return ( + + + +

{item.title}

+
+
+ + + + {item.title} + + + + + + {limitString(item.description, 128)} + + + + + + {i18n.translate('xpack.observability.news.readFullStory', { + defaultMessage: 'Read full story', + })} + + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts b/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts new file mode 100644 index 0000000000000..5c623bb9134eb --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const news = [ + { + title: 'Have SIEM questions?', + description: + 'Join our growing community of Elastic SIEM users to discuss the configuration and use of Elastic SIEM for threat detection and response.', + link_url: 'https://discuss.elastic.co/c/security/siem/?blade=securitysolutionfeed', + image_url: + 'https://aws1.discourse-cdn.com/elastic/original/3X/f/8/f8c3d0b9971cfcd0be349d973aa5799f71d280cc.png?blade=securitysolutionfeed', + }, + { + title: 'Elastic SIEM on-demand training course — free for a limited time', + description: + 'With this self-paced, on-demand course, you will learn how to leverage Elastic SIEM to drive your security operations and threat hunting. This course is designed for security analysts and practitioners who have used other SIEMs or are familiar with SIEM concepts.', + link_url: + 'https://training.elastic.co/elearning/security-analytics/elastic-siem-fundamentals-promo?blade=securitysolutionfeed', + image_url: + 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50f58e0358ebea9d/5c30508693d9791a70cd73ad/illustration-specialization-course-page-security.svg?blade=securitysolutionfeed', + }, + { + title: 'New to Elastic SIEM? Take our on-demand training course', + description: + 'With this self-paced, on-demand course, you will learn how to leverage Elastic SIEM to drive your security operations and threat hunting. This course is designed for security analysts and practitioners who have used other SIEMs or are familiar with SIEM concepts.', + link_url: + 'https://www.elastic.co/training/specializations/security-analytics/elastic-siem-fundamentals?blade=securitysolutionfeed', + image_url: + 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50f58e0358ebea9d/5c30508693d9791a70cd73ad/illustration-specialization-course-page-security.svg?blade=securitysolutionfeed', + }, +]; diff --git a/x-pack/plugins/observability/public/components/app/resources/index.test.tsx b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx index 7df8fc4e13cae..18a38722d6927 100644 --- a/x-pack/plugins/observability/public/components/app/resources/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx @@ -14,16 +14,4 @@ describe('Resources', () => { expect(getByText('Discuss forum')).toBeInTheDocument(); expect(getByText('Training and webinars')).toBeInTheDocument(); }); - it('expects all links to be defined', () => { - const { getByTestId } = render(); - const validateLink = (testId: string) => { - const href = (getByTestId(testId) as HTMLAnchorElement).href; - expect(href).not.toEqual(''); - expect(href).not.toEqual('https://www.elastic.co/'); - }; - - validateLink('button-documentation'); - validateLink('button-forum'); - validateLink('button-training'); - }); }); diff --git a/x-pack/plugins/observability/public/components/app/resources/index.tsx b/x-pack/plugins/observability/public/components/app/resources/index.tsx index 0c5cb22d70cc2..51f9cc0525112 100644 --- a/x-pack/plugins/observability/public/components/app/resources/index.tsx +++ b/x-pack/plugins/observability/public/components/app/resources/index.tsx @@ -3,33 +3,30 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiListGroup, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; const resources = [ { - id: 'documentation', - icon: 'documents', - name: i18n.translate('xpack.observability.resources.documentation', { + iconType: 'documents', + label: i18n.translate('xpack.observability.resources.documentation', { defaultMessage: 'Documentation', }), // TODO: caue what's the url? href: 'https://www.elastic.co', }, { - id: 'forum', - icon: 'editorComment', - name: i18n.translate('xpack.observability.resources.forum', { + iconType: 'editorComment', + label: i18n.translate('xpack.observability.resources.forum', { defaultMessage: 'Discuss forum', }), // TODO: caue what's the url? href: 'https://www.elastic.co', }, { - id: 'training', - icon: 'training', - name: i18n.translate('xpack.observability.resources.training', { + iconType: 'training', + label: i18n.translate('xpack.observability.resources.training', { defaultMessage: 'Training and webinars', }), // TODO: caue what's the url? @@ -41,25 +38,15 @@ export const Resources = () => { return ( - +

{i18n.translate('xpack.observability.resources.title', { defaultMessage: 'Resources', })}

-
+
- {resources.map((resource) => ( - - - {resource.name} - - - ))} +
); }; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index db7d95a929d8d..b8b67b0e31536 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -27,6 +27,7 @@ import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { emptySections } from './emptySection'; import { Resources } from '../../components/app/resources'; +import { News } from '../../components/app/news'; interface Props { routeParams: RouteParams<'/overview'>; @@ -143,8 +144,15 @@ export const OverviewPage = ({ routeParams }: Props) => { )}
- - + + + + + + + + + From 39dd39db14ccd010625b54b565e7b21a3743aeb6 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 2 Jul 2020 19:01:15 +0200 Subject: [PATCH 73/97] adding support to custom colors on EuiProgress and EuiStats --- .../components/app/section/apm/index.tsx | 60 +++---- .../public/components/app/section/index.tsx | 15 +- .../components/app/section/logs/index.tsx | 18 +- .../components/app/section/metrics/index.tsx | 152 +++++++++++++---- .../components/app/section/uptime/index.tsx | 157 +++++++++++------- .../components/app/styled_stat/index.tsx | 16 ++ 6 files changed, 279 insertions(+), 139 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/styled_stat/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 969b11f51c9f7..e013861f093d2 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -25,10 +25,9 @@ import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { ApmFetchDataResponse } from '../../../../typings'; -import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; import { onBrushEnd } from '../helper'; +import { StyledStat } from '../../styled_stat'; interface Props { startTime?: string; @@ -36,6 +35,10 @@ interface Props { bucketSize?: string; } +function formatTransactionValue(value?: number) { + return numeral(value).format('0.00a'); +} + export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { const theme = useContext(ThemeContext); const history = useHistory(); @@ -46,45 +49,44 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { } }, [startTime, endTime, bucketSize]); - const transactionSeries = data?.series.transactions; + const { title = 'APM', appLink, stats, series } = data || {}; const min = moment.utc(startTime).valueOf(); const max = moment.utc(endTime).valueOf(); const formatter = niceTimeFormatter([min, max]); - const getSerieColor = (color?: string) => { - if (color) { - return color; - } - }; - const isLoading = status === FETCH_STATUS.LOADING; + const transactionsColor = series?.transactions.color || theme.euiColorVis1; + return ( - {data && - Object.keys(data.stats).map((key) => { - const stat = data?.stats[key as keyof ApmFetchDataResponse['stats']]; - return ( - - - - ); - })} + + + + + + @@ -95,23 +97,23 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { legendPosition="bottom" xDomain={{ min, max }} /> - {transactionSeries?.coordinates && ( + {series?.transactions.coordinates && ( <> numeral(d).format('0a')} + tickFormat={(value) => numeral(value).format('0.00a')} /> diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 1e02e36b7d74a..a924983aee2ee 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -3,11 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiAccordion, EuiButtonEmpty, EuiPanel, EuiTitle } from '@elastic/eui'; +import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { EuiText } from '@elastic/eui'; -import { EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; interface Props { @@ -21,8 +19,7 @@ const StyledEuiAccordion = styled(EuiAccordion)` .euiAccordion__triggerWrapper { border-bottom: ${(props) => props.theme.eui.euiBorderThin}; } - .euiAccordion__button, - .euiAccordion__optionalAction { + .euiAccordion__button { margin-bottom: 16px; } .euiAccordion__childWrapper { @@ -43,20 +40,20 @@ export const SectionContainer = ({ title, appLink, children, subtitle, minHeight } extraAction={ appLink && ( - + {i18n.translate('xpack.observability.chart.viewInAppLabel', { defaultMessage: 'View in app', })} - + ) } > - +

{subtitle}

-
+ {children}
diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 142e53890697f..07728e6666252 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -51,6 +51,8 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const formatter = niceTimeFormatter([min, max]); + const { title = 'Logs', appLink, stats, series } = data || {}; + const customColors = { colors: { vizColors: euiPaletteColorBlind({ @@ -64,16 +66,16 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { return ( - {data && - Object.keys(data.stats).map((key) => { - const stat = data.stats[key as keyof LogsFetchDataResponse['stats']]; + {stats && + Object.keys(stats).map((key) => { + const stat = stats[key as keyof LogsFetchDataResponse['stats']]; return ( { legendPosition="bottom" xDomain={{ min, max }} /> - {data && - Object.keys(data.series).map((key) => { - const serie = data.series[key]; + {series && + Object.keys(series).map((key) => { + const serie = series[key]; const chartData = serie.coordinates.map((coordinate) => ({ ...coordinate, g: serie.label, diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 6e1099bb6ccd1..22c50e6ac77aa 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -5,15 +5,16 @@ */ import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiStat } from '@elastic/eui'; +import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; -import { ThemeContext } from 'styled-components'; +import styled, { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { MetricsFetchDataResponse, Series } from '../../../../typings'; -import { formatStatValue } from '../../../../utils/format_stat_value'; +import { Series } from '../../../../typings'; import { ChartContainer } from '../../chart_container'; +import { StyledStat } from '../../styled_stat'; interface Props { startTime?: string; @@ -21,7 +22,31 @@ interface Props { bucketSize?: string; } +/** + * EuiProgress doesn't support custom color, when it does this component can be removed. + */ +const StyledProgress = styled.div<{ color?: string }>` + progress { + &.euiProgress--native { + &::-webkit-progress-value { + background-color: ${(props) => props.color}; + } + + &::-moz-progress-bar { + background-color: ${(props) => props.color}; + } + } + + &.euiProgress--indeterminate { + &:before { + background-color: ${(props) => props.color}; + } + } + } +`; + export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { + const theme = useContext(ThemeContext); const { data, status } = useFetcher(() => { if (startTime && endTime && bucketSize) { return getDataHandler('infra_metrics')?.fetchData({ startTime, endTime, bucketSize }); @@ -30,48 +55,112 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { const isLoading = status === FETCH_STATUS.LOADING; + const { title = 'Metrics', appLink, stats, series } = data || {}; + + const cpuColor = stats?.cpu.color || theme.eui.euiColorVis7; + const memoryColor = stats?.cpu.color || theme.eui.euiColorVis0; + const inboundTrafficColor = series?.inboundTraffic.color || theme.eui.euiColorVis3; + const outboundTrafficColor = series?.outboundTraffic.color || theme.eui.euiColorVis2; + return ( - {data && - Object.keys(data.stats).map((key) => { - const statKey = key as keyof MetricsFetchDataResponse['stats']; - const stat = data.stats[statKey]; - const value = formatStatValue(stat); - - const serie = data.series[key as keyof MetricsFetchDataResponse['series']]; - - const chart = serie ? ( - - ) : ( - <> - - - - ); - - return ( - - - {statKey !== 'hosts' && chart} - - - ); - })} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; -const AreaChart = ({ serie, isLoading }: { serie: Series; isLoading: boolean }) => { +const AreaChart = ({ + serie, + isLoading, + color, +}: { + serie?: Series; + isLoading: boolean; + color: string; +}) => { const theme = useContext(ThemeContext); + if (!serie) { + return null; + } return ( @@ -88,6 +177,7 @@ const AreaChart = ({ serie, isLoading }: { serie: Series; isLoading: boolean }) xAccessor={'x'} yAccessors={['y']} data={serie.coordinates} + color={color} />
diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 2420fdf069e9b..1bbfe385d50ae 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -14,21 +14,22 @@ import { Position, ScaleType, Settings, + TickFormatter, } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import React, { Fragment, useContext } from 'react'; +import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { UptimeFetchDataResponse } from '../../../../typings'; -import { formatStatValue } from '../../../../utils/format_stat_value'; +import { Series } from '../../../../typings'; import { ChartContainer } from '../../chart_container'; import { onBrushEnd } from '../helper'; +import { StyledStat } from '../../styled_stat'; interface Props { startTime?: string; @@ -48,42 +49,55 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const min = moment.utc(startTime).valueOf(); const max = moment.utc(endTime).valueOf(); - const formatter = niceTimeFormatter([min, max]); - const getSerieColor = (color?: string) => { - if (color) { - return theme.eui[color]; - } - }; - const isLoading = status === FETCH_STATUS.LOADING; + const { title = 'Uptime', appLink, stats, series } = data || {}; + + const downColor = series?.down.color || theme.eui.euiColorVis2; + const upColor = series?.up.color || theme.eui.euiColorLightShade; + return ( - {data && - Object.keys(data.stats).map((key) => { - const stat = data.stats[key as keyof UptimeFetchDataResponse['stats']]; - return ( - - - - ); - })} + {/* Stats section */} + + + + + + + + + + + {/* Chart section */} { legendPosition="bottom" xDomain={{ min, max }} /> - {data && - Object.keys(data.series).map((key) => { - const serie = data.series[key as keyof UptimeFetchDataResponse['series']]; - const chartData = serie.coordinates.map((coordinate) => ({ - ...coordinate, - g: serie.label, - })); - return ( - - - - numeral(x).format('0a')} - /> - - ); - })} + + ); }; + +const UptimeBarSeries = ({ + id, + series, + color, + ticktFormatter, +}: { + id: string; + series?: Series; + color: string; + ticktFormatter: TickFormatter; +}) => { + if (!series) { + return null; + } + const chartData = series.coordinates.map((coordinate) => ({ + ...coordinate, + g: series.label, + })); + return ( + <> + + + numeral(x).format('0a')} + /> + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx b/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx new file mode 100644 index 0000000000000..a54993c5538bf --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import styled from 'styled-components'; +import { EuiStat } from '@elastic/eui'; + +/** + * This component is needed until EuiStat supports custom colors + */ +export const StyledStat = styled(EuiStat)` + .euiStat__title { + color: ${(props) => props.color}; + } +`; From 07109b82c509b3e181212df22dcd36a1cff4f86b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 3 Jul 2020 11:32:43 +0200 Subject: [PATCH 74/97] removing infra mock data --- .../public/hooks/use_fetcher.tsx | 20 +++++++++++++------ x-pack/plugins/observability/public/plugin.ts | 8 -------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx index 8ede2694ef964..88a8ad264e737 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx @@ -52,12 +52,20 @@ export function useFetcher( error: undefined, })); - const data = await promise; - setResult({ - data, - status: FETCH_STATUS.SUCCESS, - error: undefined, - } as FetcherResult>); + try { + const data = await promise; + setResult({ + data, + status: FETCH_STATUS.SUCCESS, + error: undefined, + } as FetcherResult>); + } catch (e) { + setResult((prevResult) => ({ + data: preservePreviousData ? prevResult.data : undefined, + status: FETCH_STATUS.FAILURE, + error: e, + })); + } } doFetch(); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index b5569911209b3..bbda1026606f1 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -11,8 +11,6 @@ import { PluginInitializerContext, } from '../../../../src/core/public'; import { registerDataHandler } from './data_handler'; -// TODO: caue: remove it later -import { fetchMetricsData } from './mock/metrics.mock'; export interface ObservabilityPluginSetup { dashboard: { register: typeof registerDataHandler }; @@ -41,12 +39,6 @@ export class Plugin implements PluginClass Promise.resolve(true), - }); - return { dashboard: { register: registerDataHandler }, }; From bfa6fbdeefade6fd4faa1637ec78657a150c0d34 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 3 Jul 2020 14:10:13 +0200 Subject: [PATCH 75/97] adding error message when api throwns an error --- .../components/app/section/apm/index.tsx | 3 +- .../app/section/error_panel/index.tsx | 22 ++++++++++++ .../public/components/app/section/index.tsx | 36 ++++++++++++++++--- .../components/app/section/logs/index.tsx | 21 +++++++++-- .../components/app/section/metrics/index.tsx | 1 + .../components/app/section/uptime/index.tsx | 1 + 6 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index e013861f093d2..5e4b187581151 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -68,6 +68,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { defaultMessage: 'Summary', })} appLink={appLink} + hasError={status === FETCH_STATUS.FAILURE} > @@ -113,7 +114,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { id="y-axis" position={Position.Left} showGridLines - tickFormat={(value) => numeral(value).format('0.00a')} + tickFormat={(value) => `${numeral(value).format('0.00a')} tpm`} /> diff --git a/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx b/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx new file mode 100644 index 0000000000000..eaa1f42e68e07 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const ErrorPanel = () => { + return ( + + + + {i18n.translate('xpack.observability.section.errorPanel', { + defaultMessage: 'An error happened when trying to fetch data. Please try again', + })} + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index a924983aee2ee..f7ce0d4918227 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -7,13 +7,15 @@ import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@ import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; +import { ErrorPanel } from './error_panel'; interface Props { title: string; subtitle: string; minHeight: number; - appLink?: string; + hasError: boolean; children: React.ReactNode; + appLink?: string; } const StyledEuiAccordion = styled(EuiAccordion)` .euiAccordion__triggerWrapper { @@ -27,7 +29,14 @@ const StyledEuiAccordion = styled(EuiAccordion)` } `; -export const SectionContainer = ({ title, appLink, children, subtitle, minHeight }: Props) => { +export const SectionContainer = ({ + title, + appLink, + children, + subtitle, + minHeight, + hasError, +}: Props) => { return ( - +

{subtitle}

- - {children} + {hasError ? ( + + ) : ( + <> + + {children} + + )}
); diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 07728e6666252..91ab68c6b20bc 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -29,6 +29,7 @@ import { LogsFetchDataResponse } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; import { onBrushEnd } from '../helper'; +import { StyledStat } from '../../styled_stat'; interface Props { startTime?: string; @@ -53,6 +54,16 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const { title = 'Logs', appLink, stats, series } = data || {}; + const availableColors = euiPaletteColorBlind({ + rotations: data ? Math.ceil(Object.keys(data.series).length / 10) : 1, + }); + const colorsPerItem = stats + ? Object.keys(stats).reduce((acc: Record, key, index) => { + acc[key] = availableColors[index]; + return acc; + }, {}) + : {}; + const customColors = { colors: { vizColors: euiPaletteColorBlind({ @@ -71,6 +82,7 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { defaultMessage: 'Logs rate', })} appLink={appLink} + hasError={status === FETCH_STATUS.FAILURE} > {stats && @@ -78,11 +90,12 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const stat = stats[key as keyof LogsFetchDataResponse['stats']]; return ( - ); @@ -92,10 +105,11 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { onBrushEnd({ x, history })} - theme={[customColors, theme.darkMode ? DARK_THEME : LIGHT_THEME]} + theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} showLegend - legendPosition="bottom" + legendPosition={Position.Right} xDomain={{ min, max }} + showLegendExtra /> {series && Object.keys(series).map((key) => { @@ -115,6 +129,7 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { stackAccessors={['x']} splitSeriesAccessors={['g']} data={chartData} + color={colorsPerItem[key]} /> { defaultMessage: 'Summary', })} appLink={appLink} + hasError={status === FETCH_STATUS.FAILURE} > diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 1bbfe385d50ae..a5753efb62dc5 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -66,6 +66,7 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { defaultMessage: 'Summary', })} appLink={appLink} + hasError={status === FETCH_STATUS.FAILURE} > {/* Stats section */} From d949b398036750eb82ac614ff57da83404d52ef7 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 3 Jul 2020 15:59:44 +0200 Subject: [PATCH 76/97] adding alert section --- .../components/app/section/alerts/index.tsx | 102 ++++++++++++++++++ .../components/app/section/apm/index.tsx | 3 +- .../public/components/app/section/index.tsx | 4 +- .../components/app/section/uptime/index.tsx | 4 +- .../public/pages/overview/index.tsx | 8 +- 5 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/app/section/alerts/index.tsx diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx new file mode 100644 index 0000000000000..2e7a5802d8c7a --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiLink } from '@elastic/eui'; +import { EuiBadge } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import moment from 'moment'; +import { EuiHorizontalRule } from '@elastic/eui'; +import { SectionContainer } from '..'; + +const alerts = [ + { + id: '1', + name: 'Error rate | opbeans-java', + alertTypeId: 'apm.error_rate', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-07-03T14:27:51.488Z', + muteAll: true, + }, + { + id: '2', + name: 'Error rate | opbeans-java', + alertTypeId: 'apm.error_rate', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-07-02T14:27:51.488Z', + muteAll: true, + }, + { + id: '3', + name: 'Error rate | opbeans-java', + alertTypeId: 'apm.error_rate', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-06-25T14:27:51.488Z', + muteAll: true, + }, + { + id: '4', + name: 'Error rate | opbeans-java', + alertTypeId: 'apm.error_rate', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-03-25T14:27:51.488Z', + muteAll: true, + }, +]; + +export const AlertsSection = () => { + return ( + + {alerts.map((alert, index) => { + const isLastElement = index === alerts.length - 1; + return ( + + + {alert.name} + + + + + {alert.alertTypeId} + + {alert.tags.map((tag, index) => { + return ( + + {tag} + + ); + })} + + + + + + + Updated {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago + + + + + + + + {!isLastElement && } + + ); + })} + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 5e4b187581151..aa9df4ca98816 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -94,8 +94,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { onBrushEnd({ x, history })} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend={true} - legendPosition="bottom" + showLegend={false} xDomain={{ min, max }} /> {series?.transactions.coordinates && ( diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index f7ce0d4918227..9e02a4a5e9dc5 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -12,9 +12,9 @@ import { ErrorPanel } from './error_panel'; interface Props { title: string; subtitle: string; - minHeight: number; hasError: boolean; children: React.ReactNode; + minHeight?: number; appLink?: string; } const StyledEuiAccordion = styled(EuiAccordion)` @@ -68,7 +68,7 @@ export const SectionContainer = ({ * When a parent container sets min-height its children can't set height:100% * https://bugs.webkit.org/show_bug.cgi?id=26559 */ - height: '1px', + height: minHeight ? '1px' : undefined, }} > diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index a5753efb62dc5..1988cfa0a1571 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -104,8 +104,8 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { onBrushEnd({ x, history })} theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} - showLegend - legendPosition="bottom" + showLegend={false} + legendPosition={Position.Right} xDomain={{ min, max }} /> ; @@ -79,11 +80,10 @@ export const OverviewPage = ({ routeParams }: Props) => { /> - - + {hasData?.infra_logs && ( @@ -144,6 +144,10 @@ export const OverviewPage = ({ routeParams }: Props) => { )} + + + + From dc86f698319b6babdecc89054b27b821fe59d18b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Fri, 3 Jul 2020 16:32:47 +0200 Subject: [PATCH 77/97] Adding alerts --- .../components/app/section/alerts/index.tsx | 4 ++-- .../public/pages/overview/index.tsx | 22 ++++++------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 2e7a5802d8c7a..4be2eb1716c30 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -72,9 +72,9 @@ export const AlertsSection = () => { {alert.alertTypeId} - {alert.tags.map((tag, index) => { + {alert.tags.map((tag, idx) => { return ( - + {tag} ); diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 48503729fc951..f1e95757a5c50 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -3,22 +3,20 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import moment from 'moment'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; -import { EuiHorizontalRule } from '@elastic/eui'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; +import { News } from '../../components/app/news'; +import { Resources } from '../../components/app/resources'; +import { AlertsSection } from '../../components/app/section/alerts'; import { APMSection } from '../../components/app/section/apm'; import { LogsSection } from '../../components/app/section/logs'; import { MetricsSection } from '../../components/app/section/metrics'; import { UptimeSection } from '../../components/app/section/uptime'; -import { - DatePicker, - TimePickerTime, - TimePickerRefreshInterval, -} from '../../components/shared/data_picker'; +import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; import { fetchHasData } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; @@ -26,9 +24,6 @@ import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { emptySections } from './emptySection'; -import { Resources } from '../../components/app/resources'; -import { News } from '../../components/app/news'; -import { AlertsSection } from '../../components/app/section/alerts'; interface Props { routeParams: RouteParams<'/overview'>; @@ -40,15 +35,12 @@ export const OverviewPage = ({ routeParams }: Props) => { const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); - const timePickerRefreshInterval = useKibanaUISettings( - UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS - ); const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to, - refreshInterval = timePickerRefreshInterval.value, - refreshPaused = timePickerRefreshInterval.pause, + refreshInterval = 15000, + refreshPaused = true, } = routeParams.query; const startTime = getParsedDate(rangeFrom); From 1bc38bb9a0e6b610b17d42c1ee2972843a858967 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 6 Jul 2020 10:15:30 +0200 Subject: [PATCH 78/97] adding alert api call --- .../components/app/section/alerts/index.tsx | 65 ++++++++++--------- .../public/pages/overview/index.tsx | 20 +++++- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 4be2eb1716c30..c9364f1c943c6 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -15,7 +15,7 @@ import moment from 'moment'; import { EuiHorizontalRule } from '@elastic/eui'; import { SectionContainer } from '..'; -const alerts = [ +const _alerts = [ { id: '1', name: 'Error rate | opbeans-java', @@ -24,33 +24,38 @@ const alerts = [ updatedAt: '2020-07-03T14:27:51.488Z', muteAll: true, }, - { - id: '2', - name: 'Error rate | opbeans-java', - alertTypeId: 'apm.error_rate', - tags: ['apm', 'service.name:opbeans-java'], - updatedAt: '2020-07-02T14:27:51.488Z', - muteAll: true, - }, - { - id: '3', - name: 'Error rate | opbeans-java', - alertTypeId: 'apm.error_rate', - tags: ['apm', 'service.name:opbeans-java'], - updatedAt: '2020-06-25T14:27:51.488Z', - muteAll: true, - }, - { - id: '4', - name: 'Error rate | opbeans-java', - alertTypeId: 'apm.error_rate', - tags: ['apm', 'service.name:opbeans-java'], - updatedAt: '2020-03-25T14:27:51.488Z', - muteAll: true, - }, ]; +// { +// id: '2', +// name: 'Error rate | opbeans-java', +// alertTypeId: 'apm.error_rate', +// tags: ['apm', 'service.name:opbeans-java'], +// updatedAt: '2020-07-02T14:27:51.488Z', +// muteAll: true, +// }, +// { +// id: '3', +// name: 'Error rate | opbeans-java', +// alertTypeId: 'apm.error_rate', +// tags: ['apm', 'service.name:opbeans-java'], +// updatedAt: '2020-06-25T14:27:51.488Z', +// muteAll: true, +// }, +// { +// id: '4', +// name: 'Error rate | opbeans-java', +// alertTypeId: 'apm.error_rate', +// tags: ['apm', 'service.name:opbeans-java'], +// updatedAt: '2020-03-25T14:27:51.488Z', +// muteAll: true, +// }, +// ]; + +interface Props { + alerts: Array; +} -export const AlertsSection = () => { +export const AlertsSection = ({ alerts }: Props) => { return ( { Updated {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago - - - + {alert.muteAll && ( + + + + )} {!isLastElement && } diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index f1e95757a5c50..1d2c10374e789 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -24,15 +24,27 @@ import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { emptySections } from './emptySection'; +import { usePluginContext } from '../../hooks/use_plugin_context'; interface Props { routeParams: RouteParams<'/overview'>; } export const OverviewPage = ({ routeParams }: Props) => { + const getAlerts = async () => { + return await core.http.get('/api/alerts/_find', { + query: { + page: 1, + per_page: 10, + }, + }); + }; + const { core } = usePluginContext(); const result = useFetcher(() => fetchHasData(), []); const hasData = result.data; + const alerts = useFetcher(() => getAlerts(), []); + const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); @@ -136,9 +148,11 @@ export const OverviewPage = ({ routeParams }: Props) => { )} - - - + {alerts?.data?.data && ( + + + + )} From 2e119cb0de59e648a19b81bc63a7df5aba18bafc Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 6 Jul 2020 13:24:16 +0200 Subject: [PATCH 79/97] addressing PR comments --- .../components/app/chart_container/index.tsx | 20 +++-- .../public/components/app/header/index.tsx | 7 +- .../public/components/app/news/index.tsx | 20 ++--- .../public/components/app/resources/index.tsx | 3 +- .../components/app/section/alerts/index.tsx | 31 +++++--- .../components/app/section/apm/index.tsx | 10 +-- .../public/components/app/section/index.tsx | 76 ++++++++----------- .../components/app/section/logs/index.tsx | 34 ++++----- .../components/app/section/metrics/index.tsx | 16 ++-- .../components/app/section/uptime/index.tsx | 12 +-- .../public/hooks/use_chart_theme.tsx | 13 ++++ .../public/pages/overview/index.tsx | 10 +-- 12 files changed, 127 insertions(+), 125 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/use_chart_theme.tsx diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx index 0194cecd997bf..86bada1b44d2a 100644 --- a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx @@ -3,11 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiLoadingChart } from '@elastic/eui'; -import { EuiFlexGroup } from '@elastic/eui'; -import { EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart } from '@elastic/eui'; import { EuiLoadingChartSize } from '@elastic/eui/src/components/loading/loading_chart'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; interface Props { isLoading: boolean; @@ -18,10 +17,19 @@ interface Props { } export const ChartContainer = ({ isLoading, height, children, iconSize = 'xl', width }: Props) => { - const style = { height, marginTop: `-${height}px`, marginBottom: 0, width }; + const theme = useContext(ThemeContext); + const style = { + height, + marginTop: `-${height}px`, + marginBottom: 0, + width, + opacity: 0.3, + backgroundColor: theme.eui.euiColorFullShade, + borderRadius: '4px', + }; return ( <> - {children} +
{isLoading === false && children}
{isLoading === true && ( ` background: ${(props) => props.color}; @@ -43,7 +44,11 @@ export const Header = ({ color, restrictWidth, showAddData = false }: Props) =>

{i18n.translate('xpack.observability.home.title', { defaultMessage: 'Observability', - })} + })}{' '} +

diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx index 8930a1c5173d2..f7e7d1cc2f1da 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -61,16 +61,6 @@ const NewsItem = ({ item }: { item: News }) => {
- - {item.title} - @@ -89,6 +79,16 @@ const NewsItem = ({ item }: { item: News }) => { + + {item.title} + diff --git a/x-pack/plugins/observability/public/components/app/resources/index.tsx b/x-pack/plugins/observability/public/components/app/resources/index.tsx index 51f9cc0525112..48504768a39ec 100644 --- a/x-pack/plugins/observability/public/components/app/resources/index.tsx +++ b/x-pack/plugins/observability/public/components/app/resources/index.tsx @@ -21,8 +21,7 @@ const resources = [ label: i18n.translate('xpack.observability.resources.forum', { defaultMessage: 'Discuss forum', }), - // TODO: caue what's the url? - href: 'https://www.elastic.co', + href: 'https://discuss.elastic.co/c/observability/', }, { iconType: 'training', diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index c9364f1c943c6..88ddbeaf4f373 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -3,16 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIconTip, + EuiLink, + EuiText, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EuiFlexGroup } from '@elastic/eui'; -import { EuiLink } from '@elastic/eui'; -import { EuiBadge } from '@elastic/eui'; -import { EuiFlexItem } from '@elastic/eui'; -import { EuiText } from '@elastic/eui'; -import { EuiIcon } from '@elastic/eui'; import moment from 'moment'; -import { EuiHorizontalRule } from '@elastic/eui'; +import React from 'react'; import { SectionContainer } from '..'; const _alerts = [ @@ -59,11 +61,11 @@ export const AlertsSection = ({ alerts }: Props) => { return ( {alerts.map((alert, index) => { const isLastElement = index === alerts.length - 1; @@ -95,7 +97,12 @@ export const AlertsSection = ({ alerts }: Props) => {
{alert.muteAll && ( - + )}
diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index aa9df4ca98816..11ee8e6e3c028 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -8,8 +8,6 @@ import { Axis, BarSeries, Chart, - DARK_THEME, - LIGHT_THEME, niceTimeFormatter, Position, ScaleType, @@ -22,6 +20,7 @@ import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; @@ -62,11 +61,8 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { return ( @@ -93,7 +89,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { onBrushEnd({ x, history })} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + theme={useChartTheme()} showLegend={false} xDomain={{ min, max }} /> diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 9e02a4a5e9dc5..2aba7a98397a0 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -6,39 +6,27 @@ import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import styled from 'styled-components'; import { ErrorPanel } from './error_panel'; interface Props { title: string; - subtitle: string; hasError: boolean; children: React.ReactNode; minHeight?: number; appLink?: string; + appLinkName?: string; } -const StyledEuiAccordion = styled(EuiAccordion)` - .euiAccordion__triggerWrapper { - border-bottom: ${(props) => props.theme.eui.euiBorderThin}; - } - .euiAccordion__button { - margin-bottom: 16px; - } - .euiAccordion__childWrapper { - margin-top: 8px; - } -`; export const SectionContainer = ({ title, appLink, children, - subtitle, minHeight, hasError, + appLinkName, }: Props) => { return ( - - {i18n.translate('xpack.observability.chart.viewInAppLabel', { - defaultMessage: 'View in app', - })} + {appLinkName + ? appLinkName + : i18n.translate('xpack.observability.chart.viewInAppLabel', { + defaultMessage: 'View in app', + })} ) } > - - -

{subtitle}

-
- {hasError ? ( - - ) : ( - <> - - {children} - - )} -
-
+ <> + + + {hasError ? ( + + ) : ( + <> + + {children} + + )} + + + ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 91ab68c6b20bc..d5d2523c3b64e 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -8,28 +8,26 @@ import { Axis, BarSeries, Chart, - DARK_THEME, - LIGHT_THEME, niceTimeFormatter, Position, ScaleType, Settings, } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind, EuiStat } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind, EuiSpacer, EuiTitle } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import React, { Fragment, useContext } from 'react'; +import React, { Fragment } from 'react'; import { useHistory } from 'react-router-dom'; -import { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { LogsFetchDataResponse } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; -import { onBrushEnd } from '../helper'; import { StyledStat } from '../../styled_stat'; +import { onBrushEnd } from '../helper'; interface Props { startTime?: string; @@ -38,7 +36,6 @@ interface Props { } export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { - const theme = useContext(ThemeContext); const history = useHistory(); const { data, status } = useFetcher(() => { @@ -64,26 +61,23 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { }, {}) : {}; - const customColors = { - colors: { - vizColors: euiPaletteColorBlind({ - rotations: data ? Math.ceil(Object.keys(data.series).length / 10) : 1, - }), - }, - }; - const isLoading = status === FETCH_STATUS.LOADING; return ( + +

+ {i18n.translate('xpack.observability.overview.logs.subtitle', { + defaultMessage: 'Log rate', + })} +

+
+ {stats && Object.keys(stats).map((key) => { @@ -105,7 +99,7 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { onBrushEnd({ x, history })} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + theme={useChartTheme()} showLegend legendPosition={Position.Right} xDomain={{ min, max }} diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index aeb92920575ad..2d2eced6f64e8 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AreaSeries, Chart, DARK_THEME, LIGHT_THEME, ScaleType, Settings } from '@elastic/charts'; +import { AreaSeries, Chart, ScaleType, Settings } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; @@ -11,6 +11,7 @@ import React, { useContext } from 'react'; import styled, { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { Series } from '../../../../typings'; import { ChartContainer } from '../../chart_container'; @@ -64,11 +65,8 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { return ( @@ -158,7 +156,7 @@ const AreaChart = ({ isLoading: boolean; color: string; }) => { - const theme = useContext(ThemeContext); + const chartTheme = useChartTheme(); if (!serie) { return null; } @@ -166,11 +164,7 @@ const AreaChart = ({ return ( - + { return ( @@ -103,7 +99,7 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { onBrushEnd({ x, history })} - theme={theme.darkMode ? DARK_THEME : LIGHT_THEME} + theme={useChartTheme()} showLegend={false} legendPosition={Position.Right} xDomain={{ min, max }} diff --git a/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx new file mode 100644 index 0000000000000..13f7159ba6043 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; +import { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; + +export function useChartTheme() { + const theme = useContext(ThemeContext); + return theme.darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; +} diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 1d2c10374e789..31dd1ab5cbd9a 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -51,7 +51,7 @@ export const OverviewPage = ({ routeParams }: Props) => { const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to, - refreshInterval = 15000, + refreshInterval = 10000, refreshPaused = true, } = routeParams.query; @@ -90,7 +90,7 @@ export const OverviewPage = ({ routeParams }: Props) => { {hasData?.infra_logs && ( - + { )} {hasData?.infra_metrics && ( - + { )} {hasData?.apm && ( - + { )} {hasData?.uptime && ( - + Date: Tue, 7 Jul 2020 09:43:26 +0200 Subject: [PATCH 80/97] adding storybook --- .../components/app/section/alerts/index.tsx | 162 +- .../components/app/section/apm/index.tsx | 5 +- .../components/app/section/index.test.tsx | 6 +- .../public/components/app/section/index.tsx | 4 +- .../components/app/section/metrics/index.tsx | 1 - .../components/app/section/uptime/index.tsx | 3 +- .../observability/public/mock/logs.mock.ts | 162 -- .../public/pages/landing/index.tsx | 2 +- .../public/pages/overview/index.tsx | 81 +- .../public/pages/overview/mock/alerts.mock.ts | 57 + .../{ => pages/overview}/mock/apm.mock.ts | 2 +- .../public/pages/overview/mock/logs.mock.ts | 2326 +++++++++++++++++ .../{ => pages/overview}/mock/metrics.mock.ts | 4 +- .../{ => pages/overview}/mock/uptime.mock.ts | 2 +- .../pages/overview/overview.stories.tsx | 85 +- 15 files changed, 2602 insertions(+), 300 deletions(-) delete mode 100644 x-pack/plugins/observability/public/mock/logs.mock.ts create mode 100644 x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts rename x-pack/plugins/observability/public/{ => pages/overview}/mock/apm.mock.ts (99%) create mode 100644 x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts rename x-pack/plugins/observability/public/{ => pages/overview}/mock/metrics.mock.ts (94%) rename x-pack/plugins/observability/public/{ => pages/overview}/mock/uptime.mock.ts (99%) diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 88ddbeaf4f373..364b3ff37a631 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -14,50 +14,34 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import React from 'react'; +import React, { useState } from 'react'; +import { EuiSelect } from '@elastic/eui'; +import { uniqBy } from 'lodash'; +import { Alert } from '../../../../../../alerts/common'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; import { SectionContainer } from '..'; -const _alerts = [ - { - id: '1', - name: 'Error rate | opbeans-java', - alertTypeId: 'apm.error_rate', - tags: ['apm', 'service.name:opbeans-java'], - updatedAt: '2020-07-03T14:27:51.488Z', - muteAll: true, - }, -]; -// { -// id: '2', -// name: 'Error rate | opbeans-java', -// alertTypeId: 'apm.error_rate', -// tags: ['apm', 'service.name:opbeans-java'], -// updatedAt: '2020-07-02T14:27:51.488Z', -// muteAll: true, -// }, -// { -// id: '3', -// name: 'Error rate | opbeans-java', -// alertTypeId: 'apm.error_rate', -// tags: ['apm', 'service.name:opbeans-java'], -// updatedAt: '2020-06-25T14:27:51.488Z', -// muteAll: true, -// }, -// { -// id: '4', -// name: 'Error rate | opbeans-java', -// alertTypeId: 'apm.error_rate', -// tags: ['apm', 'service.name:opbeans-java'], -// updatedAt: '2020-03-25T14:27:51.488Z', -// muteAll: true, -// }, -// ]; +const ALL_TYPES = 'ALL_TYPES'; +const allTypes = { + value: ALL_TYPES, + text: i18n.translate('xpack.observability.overview.alert.allTypes', { + defaultMessage: 'All types', + }), +}; interface Props { - alerts: Array; + alerts: Alert[]; } export const AlertsSection = ({ alerts }: Props) => { + const { core } = usePluginContext(); + const [filter, setFilter] = useState(ALL_TYPES); + + const filterOptions = uniqBy(alerts, (alert) => alert.consumer).map(({ consumer }) => ({ + value: consumer, + text: consumer, + })); + return ( { defaultMessage: 'Manage alerts', })} > - {alerts.map((alert, index) => { - const isLastElement = index === alerts.length - 1; - return ( - - - {alert.name} - + + + - - - {alert.alertTypeId} - - {alert.tags.map((tag, idx) => { - return ( - - {tag} - - ); + setFilter(e.target.value)} + prepend={i18n.translate('xpack.observability.overview.alert.view', { + defaultMessage: 'View', })} - + /> - - - - - Updated {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago - - - {alert.muteAll && ( + + + + + {alerts + .filter((alert) => filter === ALL_TYPES || alert.consumer === filter) + .map((alert, index) => { + const isLastElement = index === alerts.length - 1; + return ( + + + + {alert.name} + + - + + {alert.alertTypeId} + + {alert.tags.map((tag, idx) => { + return ( + + {tag} + + ); })} - /> + - )} - - - {!isLastElement && } - - ); - })} + + + + + Updated {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago + + + {alert.muteAll && ( + + + + )} + + + {!isLastElement && } + + ); + })} + + ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 11ee8e6e3c028..deed3471991cc 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -15,18 +15,17 @@ import { } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; -import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { ChartContainer } from '../../chart_container'; -import { onBrushEnd } from '../helper'; import { StyledStat } from '../../styled_stat'; +import { onBrushEnd } from '../helper'; interface Props { startTime?: string; diff --git a/x-pack/plugins/observability/public/components/app/section/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/index.test.tsx index 6d64e77100bff..8086135ce9834 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.test.tsx @@ -12,27 +12,25 @@ describe('SectionContainer', () => { it('renders section without app link', () => { const component = render( - +
I am a very nice component
); expect(component.getByText('I am a very nice component')).toBeInTheDocument(); expect(component.getByText('Foo')).toBeInTheDocument(); - expect(component.getByText('foo bar')).toBeInTheDocument(); expect(component.queryAllByText('View in app')).toEqual([]); }); it('renders section with app link', () => { const component = render( - +
I am a very nice component
); expect(component.getByText('I am a very nice component')).toBeInTheDocument(); expect(component.getByText('Foo')).toBeInTheDocument(); - expect(component.getByText('foo bar')).toBeInTheDocument(); expect(component.getByText('View in app')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 2aba7a98397a0..3de8348909812 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -7,6 +7,7 @@ import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ErrorPanel } from './error_panel'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; interface Props { title: string; @@ -25,6 +26,7 @@ export const SectionContainer = ({ hasError, appLinkName, }: Props) => { + const { core } = usePluginContext(); return ( + {appLinkName ? appLinkName diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 2d2eced6f64e8..dd8acc15b368b 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -6,7 +6,6 @@ import { AreaSeries, Chart, ScaleType, Settings } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import styled, { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 112a384fcaa27..60a7ca3d9d70b 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -16,14 +16,13 @@ import { } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; -import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { Series } from '../../../../typings'; import { ChartContainer } from '../../chart_container'; diff --git a/x-pack/plugins/observability/public/mock/logs.mock.ts b/x-pack/plugins/observability/public/mock/logs.mock.ts deleted file mode 100644 index 05e2d4aa76b9e..0000000000000 --- a/x-pack/plugins/observability/public/mock/logs.mock.ts +++ /dev/null @@ -1,162 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FetchData, LogsFetchDataResponse } from '../typings'; - -export const fetchLogsData: FetchData = () => { - return Promise.resolve(response); -}; - -const response: LogsFetchDataResponse = { - title: 'Logs', - appLink: '/app/logs', - stats: { - unknown: { label: 'Unknown', value: 73777, type: 'number' }, - 'kibana.log': { label: 'Kibana.log', value: 1018, type: 'number' }, - 'nginx.access': { label: 'Nginx.access', value: 5528, type: 'number' }, - }, - series: { - unknown: { - label: 'Unknwon', - coordinates: [ - { - x: 1588942800000, - y: 50, - }, - { - x: 1589078700000, - y: 100, - }, - { - x: 1589214600000, - y: 5000, - }, - { - x: 1589350500000, - y: 6000, - }, - { - x: 1589486400000, - y: 4000, - }, - { - x: 1589622300000, - y: 5000, - }, - { - x: 1589758200000, - y: 10000, - }, - { - x: 1589894100000, - y: 1500, - }, - { - x: 1590030000000, - y: 3000, - }, - ], - }, - 'kibana.log': { - label: 'Kibana.log', - coordinates: [ - { - x: 1588942800000, - y: 50, - }, - { - x: 1589078700000, - y: 100, - }, - { - x: 1589214600000, - y: 5000, - }, - { - x: 1589350500000, - y: 6000, - }, - { - x: 1589486400000, - y: 4000, - }, - { - x: 1589622300000, - y: 5000, - }, - { - x: 1589758200000, - y: 10000, - }, - { - x: 1589894100000, - y: 1500, - }, - { - x: 1590030000000, - y: 3000, - }, - ], - }, - 'nginx.access': { - label: 'Nginx.access', - coordinates: [ - { - x: 1588942800000, - y: 594.3174603174604, - }, - { - x: 1589078700000, - y: 637.9072847682119, - }, - { - x: 1589214600000, - y: 798.9867549668874, - }, - { - x: 1589350500000, - y: 823.3973509933775, - }, - { - x: 1589486400000, - y: 883.3112582781457, - }, - { - x: 1589622300000, - y: 840.6423841059602, - }, - { - x: 1589758200000, - y: 863.8079470198676, - }, - { - x: 1589894100000, - y: 86.54966887417218, - }, - { - x: 1590030000000, - y: 0, - }, - ], - }, - }, -}; - -export const emptyResponse: LogsFetchDataResponse = { - title: 'Logs', - appLink: '/app/logs', - stats: { - unknown: { label: 'Unknown', value: 0, type: 'number' }, - 'kibana.log': { label: 'Kibana.log', value: 0, type: 'number' }, - 'nginx.access': { label: 'Nginx.access', value: 0, type: 'number' }, - }, - series: { - unknown: { - label: 'Unknwon', - coordinates: [], - }, - }, -}; diff --git a/x-pack/plugins/observability/public/pages/landing/index.tsx b/x-pack/plugins/observability/public/pages/landing/index.tsx index 711496a625e7a..b614095641250 100644 --- a/x-pack/plugins/observability/public/pages/landing/index.tsx +++ b/x-pack/plugins/observability/public/pages/landing/index.tsx @@ -17,7 +17,7 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import styled, { ThemeContext } from 'styled-components'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { usePluginContext } from '../../hooks/use_plugin_context'; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 31dd1ab5cbd9a..aaeb31d702a82 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -67,6 +67,9 @@ export const OverviewPage = ({ routeParams }: Props) => { : undefined; const appEmptySections = emptySections.filter(({ id }) => hasData && !hasData[id]); + const showSections = hasData + ? Object.values(hasData).some((hasPluginData) => hasPluginData) + : false; return ( { - - {hasData?.infra_logs && ( - - - - )} - {hasData?.infra_metrics && ( - - - - )} - {hasData?.apm && ( - - - - )} - {hasData?.uptime && ( - - - - )} - + {showSections && ( + + {hasData?.infra_logs && ( + + + + )} + {hasData?.infra_metrics && ( + + + + )} + {hasData?.apm && ( + + + + )} + {hasData?.uptime && ( + + + + )} + + )} {!!appEmptySections.length && ( diff --git a/x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts new file mode 100644 index 0000000000000..759b8b5fdae4f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const alertsFetchData = async () => { + return Promise.resolve({ + data: [ + { + id: '1', + consumer: 'apm', + name: 'Error rate | opbeans-java', + alertTypeId: 'apm.error_rate', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-07-03T14:27:51.488Z', + muteAll: true, + }, + { + id: '2', + consumer: 'apm', + name: 'Transaction duration | opbeans-java', + alertTypeId: 'apm.transaction_duration', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-07-02T14:27:51.488Z', + muteAll: true, + }, + { + id: '3', + consumer: 'logs', + name: 'Logs obs test', + alertTypeId: 'logs.alert.document.count', + tags: ['logs', 'observability'], + updatedAt: '2020-06-30T14:27:51.488Z', + muteAll: true, + }, + { + id: '4', + consumer: 'metrics', + name: 'Metrics obs test', + alertTypeId: 'metrics.alert.inventory.threshold', + tags: ['metrics', 'observability'], + updatedAt: '2020-03-20T14:27:51.488Z', + muteAll: true, + }, + { + id: '5', + consumer: 'uptime', + name: 'Uptime obs test', + alertTypeId: 'xpack.uptime.alerts.monitorStatus', + tags: ['uptime', 'observability'], + updatedAt: '2020-03-25T17:27:51.488Z', + muteAll: true, + }, + ], + }); +}; diff --git a/x-pack/plugins/observability/public/mock/apm.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts similarity index 99% rename from x-pack/plugins/observability/public/mock/apm.mock.ts rename to x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts index 68252356428b9..a8c272887a9ac 100644 --- a/x-pack/plugins/observability/public/mock/apm.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ApmFetchDataResponse, FetchData } from '../typings'; +import { ApmFetchDataResponse, FetchData } from '../../../typings'; export const fetchApmData: FetchData = () => { return Promise.resolve(response); diff --git a/x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts new file mode 100644 index 0000000000000..5bea1fbf19ace --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts @@ -0,0 +1,2326 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FetchData, LogsFetchDataResponse } from '../../../typings'; + +export const fetchLogsData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: LogsFetchDataResponse = { + title: 'Logs', + appLink: + "/app/logs/stream?logPosition=(end:'2020-06-30T21:30:00.000Z',start:'2020-06-27T22:00:00.000Z')", + stats: { + 'haproxy.log': { + type: 'number', + label: 'haproxy.log', + value: 145.84289044289045, + }, + 'nginx.access': { + type: 'number', + label: 'nginx.access', + value: 94.67039627039627, + }, + 'kibana.log': { + type: 'number', + label: 'kibana.log', + value: 11.018181818181818, + }, + 'nginx.error': { + type: 'number', + label: 'nginx.error', + value: 8.218181818181819, + }, + }, + series: { + 'haproxy.log': { + label: 'haproxy.log', + coordinates: [ + { + x: 1593295200000, + y: 146.83333333333334, + }, + { + x: 1593297000000, + y: 146.96666666666667, + }, + { + x: 1593298800000, + y: 146.96666666666667, + }, + { + x: 1593300600000, + y: 146.86666666666667, + }, + { + x: 1593302400000, + y: 146.96666666666667, + }, + { + x: 1593304200000, + y: 147.03333333333333, + }, + { + x: 1593306000000, + y: 147.16666666666666, + }, + { + x: 1593307800000, + y: 146.96666666666667, + }, + { + x: 1593309600000, + y: 146.96666666666667, + }, + { + x: 1593311400000, + y: 146.96666666666667, + }, + { + x: 1593313200000, + y: 147.03333333333333, + }, + { + x: 1593315000000, + y: 147.13333333333333, + }, + { + x: 1593316800000, + y: 146.96666666666667, + }, + { + x: 1593318600000, + y: 146.96666666666667, + }, + { + x: 1593320400000, + y: 146.93333333333334, + }, + { + x: 1593322200000, + y: 147.06666666666666, + }, + { + x: 1593324000000, + y: 146.9, + }, + { + x: 1593325800000, + y: 147.06666666666666, + }, + { + x: 1593327600000, + y: 147.06666666666666, + }, + { + x: 1593329400000, + y: 146.93333333333334, + }, + { + x: 1593331200000, + y: 146.86666666666667, + }, + { + x: 1593333000000, + y: 146.86666666666667, + }, + { + x: 1593334800000, + y: 147, + }, + { + x: 1593336600000, + y: 146.66666666666666, + }, + { + x: 1593338400000, + y: 146.83333333333334, + }, + { + x: 1593340200000, + y: 146.9, + }, + { + x: 1593342000000, + y: 146.96666666666667, + }, + { + x: 1593343800000, + y: 146.86666666666667, + }, + { + x: 1593345600000, + y: 146.83333333333334, + }, + { + x: 1593347400000, + y: 146.86666666666667, + }, + { + x: 1593349200000, + y: 146.93333333333334, + }, + { + x: 1593351000000, + y: 146.8, + }, + { + x: 1593352800000, + y: 146.83333333333334, + }, + { + x: 1593354600000, + y: 146.83333333333334, + }, + { + x: 1593356400000, + y: 146.73333333333332, + }, + { + x: 1593358200000, + y: 146.9, + }, + { + x: 1593360000000, + y: 146.73333333333332, + }, + { + x: 1593361800000, + y: 146.63333333333333, + }, + { + x: 1593363600000, + y: 146.6, + }, + { + x: 1593365400000, + y: 147.06666666666666, + }, + { + x: 1593367200000, + y: 147, + }, + { + x: 1593369000000, + y: 146.93333333333334, + }, + { + x: 1593370800000, + y: 146.73333333333332, + }, + { + x: 1593372600000, + y: 147.06666666666666, + }, + { + x: 1593374400000, + y: 147, + }, + { + x: 1593376200000, + y: 147.06666666666666, + }, + { + x: 1593378000000, + y: 147.2, + }, + { + x: 1593379800000, + y: 147.1, + }, + { + x: 1593381600000, + y: 147, + }, + { + x: 1593383400000, + y: 147.06666666666666, + }, + { + x: 1593385200000, + y: 147.13333333333333, + }, + { + x: 1593387000000, + y: 147.2, + }, + { + x: 1593388800000, + y: 146.96666666666667, + }, + { + x: 1593390600000, + y: 146.83333333333334, + }, + { + x: 1593392400000, + y: 146.8, + }, + { + x: 1593394200000, + y: 144.3, + }, + { + x: 1593396000000, + y: 147.3, + }, + { + x: 1593397800000, + y: 147.2, + }, + { + x: 1593399600000, + y: 147.33333333333334, + }, + { + x: 1593401400000, + y: 147.1, + }, + { + x: 1593403200000, + y: 147.13333333333333, + }, + { + x: 1593405000000, + y: 147.16666666666666, + }, + { + x: 1593406800000, + y: 147.1, + }, + { + x: 1593408600000, + y: 147.3, + }, + { + x: 1593410400000, + y: 147.26666666666668, + }, + { + x: 1593412200000, + y: 147.2, + }, + { + x: 1593414000000, + y: 147.03333333333333, + }, + { + x: 1593415800000, + y: 146.9, + }, + { + x: 1593417600000, + y: 146.96666666666667, + }, + { + x: 1593419400000, + y: 147.1, + }, + { + x: 1593421200000, + y: 147.13333333333333, + }, + { + x: 1593423000000, + y: 147.03333333333333, + }, + { + x: 1593424800000, + y: 141.36666666666667, + }, + { + x: 1593426600000, + y: 144.63333333333333, + }, + { + x: 1593428400000, + y: 153.66666666666666, + }, + { + x: 1593430200000, + y: 136.76666666666668, + }, + { + x: 1593432000000, + y: 123.43333333333334, + }, + { + x: 1593433800000, + y: 123.5, + }, + { + x: 1593435600000, + y: 123.26666666666667, + }, + { + x: 1593437400000, + y: 123.23333333333333, + }, + { + x: 1593439200000, + y: 123.13333333333334, + }, + { + x: 1593441000000, + y: 123.2, + }, + { + x: 1593442800000, + y: 144.23333333333332, + }, + { + x: 1593444600000, + y: 147.06666666666666, + }, + { + x: 1593446400000, + y: 146.9, + }, + { + x: 1593448200000, + y: 146.7, + }, + { + x: 1593450000000, + y: 146.8, + }, + { + x: 1593451800000, + y: 146.73333333333332, + }, + { + x: 1593453600000, + y: 146.7, + }, + { + x: 1593455400000, + y: 146.7, + }, + { + x: 1593457200000, + y: 146.56666666666666, + }, + { + x: 1593459000000, + y: 146.8, + }, + { + x: 1593460800000, + y: 146.8, + }, + { + x: 1593462600000, + y: 146.83333333333334, + }, + { + x: 1593464400000, + y: 146.7, + }, + { + x: 1593466200000, + y: 146.9, + }, + { + x: 1593468000000, + y: 147.03333333333333, + }, + { + x: 1593469800000, + y: 146.76666666666668, + }, + { + x: 1593471600000, + y: 146.7, + }, + { + x: 1593473400000, + y: 146.63333333333333, + }, + { + x: 1593475200000, + y: 146.93333333333334, + }, + { + x: 1593477000000, + y: 146.5, + }, + { + x: 1593478800000, + y: 146.76666666666668, + }, + { + x: 1593480600000, + y: 144.83333333333334, + }, + { + x: 1593482400000, + y: 146.96666666666667, + }, + { + x: 1593484200000, + y: 147.1, + }, + { + x: 1593486000000, + y: 147.1, + }, + { + x: 1593487800000, + y: 147.3, + }, + { + x: 1593489600000, + y: 147.1, + }, + { + x: 1593491400000, + y: 147.03333333333333, + }, + { + x: 1593493200000, + y: 147.2, + }, + { + x: 1593495000000, + y: 147.06666666666666, + }, + { + x: 1593496800000, + y: 147.1, + }, + { + x: 1593498600000, + y: 147.2, + }, + { + x: 1593500400000, + y: 147.06666666666666, + }, + { + x: 1593502200000, + y: 147.06666666666666, + }, + { + x: 1593504000000, + y: 147.06666666666666, + }, + { + x: 1593505800000, + y: 147.06666666666666, + }, + { + x: 1593507600000, + y: 146.96666666666667, + }, + { + x: 1593509400000, + y: 147.16666666666666, + }, + { + x: 1593511200000, + y: 147.03333333333333, + }, + { + x: 1593513000000, + y: 147, + }, + { + x: 1593514800000, + y: 147.03333333333333, + }, + { + x: 1593516600000, + y: 146.96666666666667, + }, + { + x: 1593518400000, + y: 146.63333333333333, + }, + { + x: 1593520200000, + y: 146.43333333333334, + }, + { + x: 1593522000000, + y: 147.13333333333333, + }, + { + x: 1593523800000, + y: 147.13333333333333, + }, + { + x: 1593525600000, + y: 146.93333333333334, + }, + { + x: 1593527400000, + y: 147, + }, + { + x: 1593529200000, + y: 147.03333333333333, + }, + { + x: 1593531000000, + y: 147.2, + }, + { + x: 1593532800000, + y: 147.13333333333333, + }, + { + x: 1593534600000, + y: 147.13333333333333, + }, + { + x: 1593536400000, + y: 147.13333333333333, + }, + { + x: 1593538200000, + y: 147.1, + }, + { + x: 1593540000000, + y: 147, + }, + { + x: 1593541800000, + y: 147.26666666666668, + }, + { + x: 1593543600000, + y: 146.73333333333332, + }, + { + x: 1593545400000, + y: 147.03333333333333, + }, + { + x: 1593547200000, + y: 147, + }, + { + x: 1593549000000, + y: 146.9, + }, + { + x: 1593550800000, + y: 147.03333333333333, + }, + ], + }, + 'nginx.access': { + label: 'nginx.access', + coordinates: [ + { + x: 1593295200000, + y: 94.06666666666666, + }, + { + x: 1593297000000, + y: 91.4, + }, + { + x: 1593298800000, + y: 95.03333333333333, + }, + { + x: 1593300600000, + y: 94.5, + }, + { + x: 1593302400000, + y: 94.06666666666666, + }, + { + x: 1593304200000, + y: 93.3, + }, + { + x: 1593306000000, + y: 91.16666666666667, + }, + { + x: 1593307800000, + y: 94.5, + }, + { + x: 1593309600000, + y: 93.53333333333333, + }, + { + x: 1593311400000, + y: 118.9, + }, + { + x: 1593313200000, + y: 110.66666666666667, + }, + { + x: 1593315000000, + y: 95.66666666666667, + }, + { + x: 1593316800000, + y: 99.53333333333333, + }, + { + x: 1593318600000, + y: 123.36666666666666, + }, + { + x: 1593320400000, + y: 94.13333333333334, + }, + { + x: 1593322200000, + y: 95.53333333333333, + }, + { + x: 1593324000000, + y: 93.93333333333334, + }, + { + x: 1593325800000, + y: 94.06666666666666, + }, + { + x: 1593327600000, + y: 118.16666666666667, + }, + { + x: 1593329400000, + y: 108.6, + }, + { + x: 1593331200000, + y: 93.53333333333333, + }, + { + x: 1593333000000, + y: 93.06666666666666, + }, + { + x: 1593334800000, + y: 93.76666666666667, + }, + { + x: 1593336600000, + y: 95.3, + }, + { + x: 1593338400000, + y: 96.4, + }, + { + x: 1593340200000, + y: 121.93333333333334, + }, + { + x: 1593342000000, + y: 134.43333333333334, + }, + { + x: 1593343800000, + y: 160.4, + }, + { + x: 1593345600000, + y: 129.7, + }, + { + x: 1593347400000, + y: 119.16666666666667, + }, + { + x: 1593349200000, + y: 133.06666666666666, + }, + { + x: 1593351000000, + y: 212.4, + }, + { + x: 1593352800000, + y: 95.36666666666666, + }, + { + x: 1593354600000, + y: 93.6, + }, + { + x: 1593356400000, + y: 93.4, + }, + { + x: 1593358200000, + y: 95.1, + }, + { + x: 1593360000000, + y: 94.36666666666666, + }, + { + x: 1593361800000, + y: 97.23333333333333, + }, + { + x: 1593363600000, + y: 94.03333333333333, + }, + { + x: 1593365400000, + y: 94.53333333333333, + }, + { + x: 1593367200000, + y: 93.56666666666666, + }, + { + x: 1593369000000, + y: 98.43333333333334, + }, + { + x: 1593370800000, + y: 92.3, + }, + { + x: 1593372600000, + y: 93.13333333333334, + }, + { + x: 1593374400000, + y: 93.16666666666667, + }, + { + x: 1593376200000, + y: 93.7, + }, + { + x: 1593378000000, + y: 94.46666666666667, + }, + { + x: 1593379800000, + y: 97.16666666666667, + }, + { + x: 1593381600000, + y: 94.36666666666666, + }, + { + x: 1593383400000, + y: 93.7, + }, + { + x: 1593385200000, + y: 93.4, + }, + { + x: 1593387000000, + y: 91.3, + }, + { + x: 1593388800000, + y: 92.66666666666667, + }, + { + x: 1593390600000, + y: 93.73333333333333, + }, + { + x: 1593392400000, + y: 94.33333333333333, + }, + { + x: 1593394200000, + y: 93.23333333333333, + }, + { + x: 1593396000000, + y: 93.9, + }, + { + x: 1593397800000, + y: 92.83333333333333, + }, + { + x: 1593399600000, + y: 93, + }, + { + x: 1593401400000, + y: 91.2, + }, + { + x: 1593403200000, + y: 91.96666666666667, + }, + { + x: 1593405000000, + y: 93.83333333333333, + }, + { + x: 1593406800000, + y: 93.16666666666667, + }, + { + x: 1593408600000, + y: 95.36666666666666, + }, + { + x: 1593410400000, + y: 92.5, + }, + { + x: 1593412200000, + y: 93.16666666666667, + }, + { + x: 1593414000000, + y: 92.8, + }, + { + x: 1593415800000, + y: 95.83333333333333, + }, + { + x: 1593417600000, + y: 96.96666666666667, + }, + { + x: 1593419400000, + y: 94.63333333333334, + }, + { + x: 1593421200000, + y: 98.7, + }, + { + x: 1593423000000, + y: 100.03333333333333, + }, + { + x: 1593424800000, + y: 108.66666666666667, + }, + { + x: 1593426600000, + y: 110.9, + }, + { + x: 1593428400000, + y: 88.56666666666666, + }, + { + x: 1593430200000, + y: 1, + }, + { + x: 1593442800000, + y: 74.53333333333333, + }, + { + x: 1593444600000, + y: 99.03333333333333, + }, + { + x: 1593446400000, + y: 98.03333333333333, + }, + { + x: 1593448200000, + y: 91.26666666666667, + }, + { + x: 1593450000000, + y: 107.76666666666667, + }, + { + x: 1593451800000, + y: 98.26666666666667, + }, + { + x: 1593453600000, + y: 99.46666666666667, + }, + { + x: 1593455400000, + y: 102.33333333333333, + }, + { + x: 1593457200000, + y: 108.13333333333334, + }, + { + x: 1593459000000, + y: 95.36666666666666, + }, + { + x: 1593460800000, + y: 98.23333333333333, + }, + { + x: 1593462600000, + y: 91.46666666666667, + }, + { + x: 1593464400000, + y: 115.63333333333334, + }, + { + x: 1593466200000, + y: 116.23333333333333, + }, + { + x: 1593468000000, + y: 91.66666666666667, + }, + { + x: 1593469800000, + y: 94.33333333333333, + }, + { + x: 1593471600000, + y: 96.43333333333334, + }, + { + x: 1593473400000, + y: 94.7, + }, + { + x: 1593475200000, + y: 93.76666666666667, + }, + { + x: 1593477000000, + y: 91.5, + }, + { + x: 1593478800000, + y: 91.9, + }, + { + x: 1593480600000, + y: 91.3, + }, + { + x: 1593482400000, + y: 98.3, + }, + { + x: 1593484200000, + y: 95.53333333333333, + }, + { + x: 1593486000000, + y: 95.66666666666667, + }, + { + x: 1593487800000, + y: 92.73333333333333, + }, + { + x: 1593489600000, + y: 93.6, + }, + { + x: 1593491400000, + y: 94.3, + }, + { + x: 1593493200000, + y: 93.13333333333334, + }, + { + x: 1593495000000, + y: 104.36666666666666, + }, + { + x: 1593496800000, + y: 107.26666666666667, + }, + { + x: 1593498600000, + y: 101.83333333333333, + }, + { + x: 1593500400000, + y: 105.46666666666667, + }, + { + x: 1593502200000, + y: 111.86666666666666, + }, + { + x: 1593504000000, + y: 111.56666666666666, + }, + { + x: 1593505800000, + y: 103.76666666666667, + }, + { + x: 1593507600000, + y: 93.9, + }, + { + x: 1593509400000, + y: 97.16666666666667, + }, + { + x: 1593511200000, + y: 93.03333333333333, + }, + { + x: 1593513000000, + y: 94.4, + }, + { + x: 1593514800000, + y: 94.76666666666667, + }, + { + x: 1593516600000, + y: 94.96666666666667, + }, + { + x: 1593518400000, + y: 101.3, + }, + { + x: 1593520200000, + y: 98.63333333333334, + }, + { + x: 1593522000000, + y: 94.8, + }, + { + x: 1593523800000, + y: 97.46666666666667, + }, + { + x: 1593525600000, + y: 95.86666666666666, + }, + { + x: 1593527400000, + y: 97.3, + }, + { + x: 1593529200000, + y: 96.1, + }, + { + x: 1593531000000, + y: 97.1, + }, + { + x: 1593532800000, + y: 97.56666666666666, + }, + { + x: 1593534600000, + y: 107.6, + }, + { + x: 1593536400000, + y: 97.46666666666667, + }, + { + x: 1593538200000, + y: 96.46666666666667, + }, + { + x: 1593540000000, + y: 93.83333333333333, + }, + { + x: 1593541800000, + y: 98.73333333333333, + }, + { + x: 1593543600000, + y: 99.86666666666666, + }, + { + x: 1593545400000, + y: 98.66666666666667, + }, + { + x: 1593547200000, + y: 102.8, + }, + { + x: 1593549000000, + y: 96.13333333333334, + }, + { + x: 1593550800000, + y: 94.53333333333333, + }, + ], + }, + 'kibana.log': { + label: 'kibana.log', + coordinates: [ + { + x: 1593295200000, + y: 11.8, + }, + { + x: 1593297000000, + y: 11.833333333333334, + }, + { + x: 1593298800000, + y: 12.1, + }, + { + x: 1593300600000, + y: 12.133333333333333, + }, + { + x: 1593302400000, + y: 11.2, + }, + { + x: 1593304200000, + y: 11.933333333333334, + }, + { + x: 1593306000000, + y: 11.466666666666667, + }, + { + x: 1593307800000, + y: 12.066666666666666, + }, + { + x: 1593309600000, + y: 11.9, + }, + { + x: 1593311400000, + y: 11.766666666666667, + }, + { + x: 1593313200000, + y: 12.066666666666666, + }, + { + x: 1593315000000, + y: 11.7, + }, + { + x: 1593316800000, + y: 11.6, + }, + { + x: 1593318600000, + y: 11.766666666666667, + }, + { + x: 1593320400000, + y: 11.633333333333333, + }, + { + x: 1593322200000, + y: 11.833333333333334, + }, + { + x: 1593324000000, + y: 11.8, + }, + { + x: 1593325800000, + y: 11.7, + }, + { + x: 1593327600000, + y: 11.666666666666666, + }, + { + x: 1593329400000, + y: 11.8, + }, + { + x: 1593331200000, + y: 11.966666666666667, + }, + { + x: 1593333000000, + y: 11.766666666666667, + }, + { + x: 1593334800000, + y: 11.766666666666667, + }, + { + x: 1593336600000, + y: 11.866666666666667, + }, + { + x: 1593338400000, + y: 11.433333333333334, + }, + { + x: 1593340200000, + y: 12.033333333333333, + }, + { + x: 1593342000000, + y: 12.1, + }, + { + x: 1593343800000, + y: 12.1, + }, + { + x: 1593345600000, + y: 11.8, + }, + { + x: 1593347400000, + y: 12.366666666666667, + }, + { + x: 1593349200000, + y: 12.033333333333333, + }, + { + x: 1593351000000, + y: 12, + }, + { + x: 1593352800000, + y: 11.8, + }, + { + x: 1593354600000, + y: 11.5, + }, + { + x: 1593356400000, + y: 12.1, + }, + { + x: 1593358200000, + y: 11.966666666666667, + }, + { + x: 1593360000000, + y: 11.9, + }, + { + x: 1593361800000, + y: 12.233333333333333, + }, + { + x: 1593363600000, + y: 11.533333333333333, + }, + { + x: 1593365400000, + y: 11.633333333333333, + }, + { + x: 1593367200000, + y: 11.866666666666667, + }, + { + x: 1593369000000, + y: 12, + }, + { + x: 1593370800000, + y: 11.7, + }, + { + x: 1593372600000, + y: 11.8, + }, + { + x: 1593374400000, + y: 11.4, + }, + { + x: 1593376200000, + y: 11.766666666666667, + }, + { + x: 1593378000000, + y: 12.033333333333333, + }, + { + x: 1593379800000, + y: 11.833333333333334, + }, + { + x: 1593381600000, + y: 11.9, + }, + { + x: 1593383400000, + y: 11.966666666666667, + }, + { + x: 1593385200000, + y: 11.8, + }, + { + x: 1593387000000, + y: 12, + }, + { + x: 1593388800000, + y: 11.933333333333334, + }, + { + x: 1593390600000, + y: 12.033333333333333, + }, + { + x: 1593392400000, + y: 12, + }, + { + x: 1593394200000, + y: 11.533333333333333, + }, + { + x: 1593396000000, + y: 11.4, + }, + { + x: 1593397800000, + y: 11.666666666666666, + }, + { + x: 1593399600000, + y: 11.633333333333333, + }, + { + x: 1593401400000, + y: 11.166666666666666, + }, + { + x: 1593403200000, + y: 11.3, + }, + { + x: 1593405000000, + y: 11.2, + }, + { + x: 1593406800000, + y: 10.966666666666667, + }, + { + x: 1593408600000, + y: 11.5, + }, + { + x: 1593410400000, + y: 11.1, + }, + { + x: 1593412200000, + y: 11.2, + }, + { + x: 1593414000000, + y: 11.4, + }, + { + x: 1593415800000, + y: 10.8, + }, + { + x: 1593417600000, + y: 11.066666666666666, + }, + { + x: 1593419400000, + y: 11.8, + }, + { + x: 1593421200000, + y: 11.266666666666667, + }, + { + x: 1593423000000, + y: 11.333333333333334, + }, + { + x: 1593424800000, + y: 11.233333333333333, + }, + { + x: 1593426600000, + y: 11.5, + }, + { + x: 1593428400000, + y: 8.2, + }, + { + x: 1593442800000, + y: 8.2, + }, + { + x: 1593444600000, + y: 11.4, + }, + { + x: 1593446400000, + y: 10.733333333333333, + }, + { + x: 1593448200000, + y: 10.833333333333334, + }, + { + x: 1593450000000, + y: 11.3, + }, + { + x: 1593451800000, + y: 11.633333333333333, + }, + { + x: 1593453600000, + y: 11.266666666666667, + }, + { + x: 1593455400000, + y: 11.3, + }, + { + x: 1593457200000, + y: 11.333333333333334, + }, + { + x: 1593459000000, + y: 11.133333333333333, + }, + { + x: 1593460800000, + y: 10.933333333333334, + }, + { + x: 1593462600000, + y: 11.2, + }, + { + x: 1593464400000, + y: 11.166666666666666, + }, + { + x: 1593466200000, + y: 11.766666666666667, + }, + { + x: 1593468000000, + y: 11.433333333333334, + }, + { + x: 1593469800000, + y: 10.8, + }, + { + x: 1593471600000, + y: 11.266666666666667, + }, + { + x: 1593473400000, + y: 11.333333333333334, + }, + { + x: 1593475200000, + y: 11.133333333333333, + }, + { + x: 1593477000000, + y: 11.133333333333333, + }, + { + x: 1593478800000, + y: 10.9, + }, + { + x: 1593480600000, + y: 11.3, + }, + { + x: 1593482400000, + y: 12.166666666666666, + }, + { + x: 1593484200000, + y: 11.433333333333334, + }, + { + x: 1593486000000, + y: 12.133333333333333, + }, + { + x: 1593487800000, + y: 11.666666666666666, + }, + { + x: 1593489600000, + y: 11.533333333333333, + }, + { + x: 1593491400000, + y: 11.833333333333334, + }, + { + x: 1593493200000, + y: 11.766666666666667, + }, + { + x: 1593495000000, + y: 11.9, + }, + { + x: 1593496800000, + y: 11.433333333333334, + }, + { + x: 1593498600000, + y: 12, + }, + { + x: 1593500400000, + y: 12.1, + }, + { + x: 1593502200000, + y: 11.6, + }, + { + x: 1593504000000, + y: 12, + }, + { + x: 1593505800000, + y: 12.233333333333333, + }, + { + x: 1593507600000, + y: 11.633333333333333, + }, + { + x: 1593509400000, + y: 11.2, + }, + { + x: 1593511200000, + y: 11.766666666666667, + }, + { + x: 1593513000000, + y: 11.9, + }, + { + x: 1593514800000, + y: 11.366666666666667, + }, + { + x: 1593516600000, + y: 11.833333333333334, + }, + { + x: 1593518400000, + y: 11.5, + }, + { + x: 1593520200000, + y: 12, + }, + { + x: 1593522000000, + y: 12.033333333333333, + }, + { + x: 1593523800000, + y: 11.733333333333333, + }, + { + x: 1593525600000, + y: 11.566666666666666, + }, + { + x: 1593527400000, + y: 11.6, + }, + { + x: 1593529200000, + y: 11.333333333333334, + }, + { + x: 1593531000000, + y: 11.833333333333334, + }, + { + x: 1593532800000, + y: 11.233333333333333, + }, + { + x: 1593534600000, + y: 11.833333333333334, + }, + { + x: 1593536400000, + y: 11.266666666666667, + }, + { + x: 1593538200000, + y: 12, + }, + { + x: 1593540000000, + y: 11.633333333333333, + }, + { + x: 1593541800000, + y: 11.9, + }, + { + x: 1593543600000, + y: 11.966666666666667, + }, + { + x: 1593545400000, + y: 11.5, + }, + { + x: 1593547200000, + y: 11.466666666666667, + }, + { + x: 1593549000000, + y: 11.4, + }, + { + x: 1593550800000, + y: 11.833333333333334, + }, + ], + }, + 'nginx.error': { + label: 'nginx.error', + coordinates: [ + { + x: 1593295200000, + y: 9.266666666666667, + }, + { + x: 1593297000000, + y: 8.833333333333334, + }, + { + x: 1593298800000, + y: 9.033333333333333, + }, + { + x: 1593300600000, + y: 8.933333333333334, + }, + { + x: 1593302400000, + y: 8.9, + }, + { + x: 1593304200000, + y: 9.6, + }, + { + x: 1593306000000, + y: 9.066666666666666, + }, + { + x: 1593307800000, + y: 8.966666666666667, + }, + { + x: 1593309600000, + y: 8.933333333333334, + }, + { + x: 1593311400000, + y: 8.5, + }, + { + x: 1593313200000, + y: 8.133333333333333, + }, + { + x: 1593315000000, + y: 8.233333333333333, + }, + { + x: 1593316800000, + y: 8.433333333333334, + }, + { + x: 1593318600000, + y: 8.4, + }, + { + x: 1593320400000, + y: 9.266666666666667, + }, + { + x: 1593322200000, + y: 8.566666666666666, + }, + { + x: 1593324000000, + y: 8.966666666666667, + }, + { + x: 1593325800000, + y: 8.833333333333334, + }, + { + x: 1593327600000, + y: 7.5, + }, + { + x: 1593329400000, + y: 8.033333333333333, + }, + { + x: 1593331200000, + y: 8.633333333333333, + }, + { + x: 1593333000000, + y: 8.5, + }, + { + x: 1593334800000, + y: 8.866666666666667, + }, + { + x: 1593336600000, + y: 8.3, + }, + { + x: 1593338400000, + y: 8.966666666666667, + }, + { + x: 1593340200000, + y: 8.2, + }, + { + x: 1593342000000, + y: 7.566666666666666, + }, + { + x: 1593343800000, + y: 7.5, + }, + { + x: 1593345600000, + y: 7.933333333333334, + }, + { + x: 1593347400000, + y: 7.866666666666666, + }, + { + x: 1593349200000, + y: 7.566666666666666, + }, + { + x: 1593351000000, + y: 7.533333333333333, + }, + { + x: 1593352800000, + y: 8.866666666666667, + }, + { + x: 1593354600000, + y: 8.566666666666666, + }, + { + x: 1593356400000, + y: 8.233333333333333, + }, + { + x: 1593358200000, + y: 8.9, + }, + { + x: 1593360000000, + y: 8.533333333333333, + }, + { + x: 1593361800000, + y: 8.733333333333333, + }, + { + x: 1593363600000, + y: 9.333333333333334, + }, + { + x: 1593365400000, + y: 9.133333333333333, + }, + { + x: 1593367200000, + y: 9.166666666666666, + }, + { + x: 1593369000000, + y: 9.266666666666667, + }, + { + x: 1593370800000, + y: 8.966666666666667, + }, + { + x: 1593372600000, + y: 9.2, + }, + { + x: 1593374400000, + y: 9.433333333333334, + }, + { + x: 1593376200000, + y: 9.166666666666666, + }, + { + x: 1593378000000, + y: 9.266666666666667, + }, + { + x: 1593379800000, + y: 9.5, + }, + { + x: 1593381600000, + y: 9.333333333333334, + }, + { + x: 1593383400000, + y: 8.8, + }, + { + x: 1593385200000, + y: 8.733333333333333, + }, + { + x: 1593387000000, + y: 8.633333333333333, + }, + { + x: 1593388800000, + y: 8.9, + }, + { + x: 1593390600000, + y: 8.533333333333333, + }, + { + x: 1593392400000, + y: 9.3, + }, + { + x: 1593394200000, + y: 9.266666666666667, + }, + { + x: 1593396000000, + y: 8.966666666666667, + }, + { + x: 1593397800000, + y: 8.666666666666666, + }, + { + x: 1593399600000, + y: 9.166666666666666, + }, + { + x: 1593401400000, + y: 8.733333333333333, + }, + { + x: 1593403200000, + y: 8.866666666666667, + }, + { + x: 1593405000000, + y: 8.633333333333333, + }, + { + x: 1593406800000, + y: 8.8, + }, + { + x: 1593408600000, + y: 8.466666666666667, + }, + { + x: 1593410400000, + y: 8.966666666666667, + }, + { + x: 1593412200000, + y: 8.166666666666666, + }, + { + x: 1593414000000, + y: 8.7, + }, + { + x: 1593415800000, + y: 8.333333333333334, + }, + { + x: 1593417600000, + y: 8.666666666666666, + }, + { + x: 1593419400000, + y: 8.533333333333333, + }, + { + x: 1593421200000, + y: 8.233333333333333, + }, + { + x: 1593423000000, + y: 8.3, + }, + { + x: 1593424800000, + y: 7.7, + }, + { + x: 1593426600000, + y: 7.7, + }, + { + x: 1593428400000, + y: 6.133333333333334, + }, + { + x: 1593430200000, + y: 0.4666666666666667, + }, + { + x: 1593442800000, + y: 7.233333333333333, + }, + { + x: 1593444600000, + y: 8.333333333333334, + }, + { + x: 1593446400000, + y: 8.666666666666666, + }, + { + x: 1593448200000, + y: 8.466666666666667, + }, + { + x: 1593450000000, + y: 8.666666666666666, + }, + { + x: 1593451800000, + y: 8.5, + }, + { + x: 1593453600000, + y: 8.6, + }, + { + x: 1593455400000, + y: 8.5, + }, + { + x: 1593457200000, + y: 8.6, + }, + { + x: 1593459000000, + y: 8.866666666666667, + }, + { + x: 1593460800000, + y: 9.166666666666666, + }, + { + x: 1593462600000, + y: 8.4, + }, + { + x: 1593464400000, + y: 8.533333333333333, + }, + { + x: 1593466200000, + y: 8.066666666666666, + }, + { + x: 1593468000000, + y: 8.666666666666666, + }, + { + x: 1593469800000, + y: 8.966666666666667, + }, + { + x: 1593471600000, + y: 8.4, + }, + { + x: 1593473400000, + y: 8.833333333333334, + }, + { + x: 1593475200000, + y: 8.533333333333333, + }, + { + x: 1593477000000, + y: 8.066666666666666, + }, + { + x: 1593478800000, + y: 8.533333333333333, + }, + { + x: 1593480600000, + y: 8.633333333333333, + }, + { + x: 1593482400000, + y: 8.933333333333334, + }, + { + x: 1593484200000, + y: 8.833333333333334, + }, + { + x: 1593486000000, + y: 8.4, + }, + { + x: 1593487800000, + y: 8.633333333333333, + }, + { + x: 1593489600000, + y: 9.333333333333334, + }, + { + x: 1593491400000, + y: 9.366666666666667, + }, + { + x: 1593493200000, + y: 8.333333333333334, + }, + { + x: 1593495000000, + y: 9.266666666666667, + }, + { + x: 1593496800000, + y: 8.2, + }, + { + x: 1593498600000, + y: 8.4, + }, + { + x: 1593500400000, + y: 8.433333333333334, + }, + { + x: 1593502200000, + y: 7.633333333333334, + }, + { + x: 1593504000000, + y: 7.766666666666667, + }, + { + x: 1593505800000, + y: 8.4, + }, + { + x: 1593507600000, + y: 8.3, + }, + { + x: 1593509400000, + y: 8.833333333333334, + }, + { + x: 1593511200000, + y: 8.433333333333334, + }, + { + x: 1593513000000, + y: 8.766666666666667, + }, + { + x: 1593514800000, + y: 9.066666666666666, + }, + { + x: 1593516600000, + y: 8.4, + }, + { + x: 1593518400000, + y: 8.4, + }, + { + x: 1593520200000, + y: 8.8, + }, + { + x: 1593522000000, + y: 8.466666666666667, + }, + { + x: 1593523800000, + y: 8.633333333333333, + }, + { + x: 1593525600000, + y: 9.133333333333333, + }, + { + x: 1593527400000, + y: 8.7, + }, + { + x: 1593529200000, + y: 8.566666666666666, + }, + { + x: 1593531000000, + y: 9.033333333333333, + }, + { + x: 1593532800000, + y: 8.9, + }, + { + x: 1593534600000, + y: 8.7, + }, + { + x: 1593536400000, + y: 8.7, + }, + { + x: 1593538200000, + y: 8.8, + }, + { + x: 1593540000000, + y: 9.166666666666666, + }, + { + x: 1593541800000, + y: 9.033333333333333, + }, + { + x: 1593543600000, + y: 8.733333333333333, + }, + { + x: 1593545400000, + y: 9.2, + }, + { + x: 1593547200000, + y: 8.933333333333334, + }, + { + x: 1593549000000, + y: 9.2, + }, + { + x: 1593550800000, + y: 9.333333333333334, + }, + ], + }, + sample_web_logs: { + label: 'sample_web_logs', + coordinates: [ + { + x: 1593430200000, + y: 0.5666666666666667, + }, + { + x: 1593432000000, + y: 0.36666666666666664, + }, + { + x: 1593433800000, + y: 0.5666666666666667, + }, + { + x: 1593435600000, + y: 0.4666666666666667, + }, + { + x: 1593437400000, + y: 0.36666666666666664, + }, + { + x: 1593439200000, + y: 0.3, + }, + { + x: 1593441000000, + y: 0.13333333333333333, + }, + ], + }, + 'postgresql.log': { + label: 'postgresql.log', + coordinates: [ + { + x: 1593439200000, + y: 0.1, + }, + { + x: 1593441000000, + y: 0.1, + }, + ], + }, + }, +}; + +export const emptyResponse: LogsFetchDataResponse = { + title: 'Logs', + appLink: '/app/logs', + stats: {}, + series: {}, +}; diff --git a/x-pack/plugins/observability/public/mock/metrics.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts similarity index 94% rename from x-pack/plugins/observability/public/mock/metrics.mock.ts rename to x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts index 4ca1380839096..81077e4905545 100644 --- a/x-pack/plugins/observability/public/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MetricsFetchDataResponse, FetchData } from '../typings'; +import { MetricsFetchDataResponse, FetchData } from '../../../typings'; export const fetchMetricsData: FetchData = () => { return Promise.resolve(response); @@ -17,7 +17,6 @@ const response: MetricsFetchDataResponse = { hosts: { label: 'Hosts', value: 11, type: 'number' }, cpu: { label: 'CPU usage', value: 0.8, type: 'percent' }, memory: { label: 'Memory Usage', value: 0.362, type: 'percent' }, - disk: { label: 'Disk Usage', value: 0.324, type: 'percent' }, inboundTraffic: { label: 'Inbount traffic', value: 1024, type: 'bytesPerSecond' }, outboundTraffic: { label: 'Outbount traffic', value: 1024, type: 'bytesPerSecond' }, }, @@ -122,7 +121,6 @@ export const emptyResponse: MetricsFetchDataResponse = { hosts: { label: 'Hosts', value: 0, type: 'number' }, cpu: { label: 'CPU usage', value: 0, type: 'percent' }, memory: { label: 'Memory Usage', value: 0, type: 'percent' }, - disk: { label: 'Disk Usage', value: 0, type: 'percent' }, inboundTraffic: { label: 'Inbount traffic', value: 0, type: 'bytesPerSecond' }, outboundTraffic: { label: 'Outbount traffic', value: 0, type: 'bytesPerSecond' }, }, diff --git a/x-pack/plugins/observability/public/mock/uptime.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts similarity index 99% rename from x-pack/plugins/observability/public/mock/uptime.mock.ts rename to x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts index b57d8ccf8f5c6..2d5c744c52998 100644 --- a/x-pack/plugins/observability/public/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { UptimeFetchDataResponse, FetchData } from '../typings'; +import { UptimeFetchDataResponse, FetchData } from '../../../typings'; export const fetchUptimeData: FetchData = () => { return Promise.resolve(response); diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index b8eb6c76dc63e..17543aa620d31 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -10,14 +10,20 @@ import { MemoryRouter } from 'react-router-dom'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; import { PluginContext } from '../../context/plugin_context'; import { registerDataHandler, unregisterDataHandler } from '../../data_handler'; -import { emptyResponse as emptyAPMResponse, fetchApmData } from '../../mock/apm.mock'; -import { fetchLogsData, emptyResponse as emptyLogsResponse } from '../../mock/logs.mock'; -import { fetchMetricsData, emptyResponse as emptyMetricsResponse } from '../../mock/metrics.mock'; -import { fetchUptimeData, emptyResponse as emptyUptimeResponse } from '../../mock/uptime.mock'; +import { emptyResponse as emptyAPMResponse, fetchApmData } from './mock/apm.mock'; +import { fetchLogsData, emptyResponse as emptyLogsResponse } from './mock/logs.mock'; +import { fetchMetricsData, emptyResponse as emptyMetricsResponse } from './mock/metrics.mock'; +import { fetchUptimeData, emptyResponse as emptyUptimeResponse } from './mock/uptime.mock'; import { EuiThemeProvider } from '../../typings'; import { OverviewPage } from './'; +import { alertsFetchData } from './mock/alerts.mock'; const core = { + http: { + basePath: { + prepend: (link) => `http://localhost:5601${link}`, + }, + }, uiSettings: { get: (key: string) => { const euiSettings = { @@ -88,6 +94,14 @@ const core = { }, } as AppMountContext['core']; +const coreWithAlerts = ({ + ...core, + http: { + ...core.http, + get: alertsFetchData, + }, +} as unknown) as AppMountContext['core']; + function unregisterAll() { unregisterDataHandler({ appName: 'apm' }); unregisterDataHandler({ appName: 'infra_logs' }); @@ -164,13 +178,12 @@ storiesOf('app/Overview', module) storiesOf('app/Overview', module) .addDecorator((storyFn) => ( - + {storyFn()}) )) .add('logs, metrics and alerts', () => { - // TODO: add alert here unregisterAll(); registerDataHandler({ appName: 'infra_logs', @@ -194,7 +207,7 @@ storiesOf('app/Overview', module) storiesOf('app/Overview', module) .addDecorator((storyFn) => ( - + {storyFn()}) @@ -268,7 +281,7 @@ storiesOf('app/Overview', module) storiesOf('app/Overview', module) .addDecorator((storyFn) => ( - + {storyFn()}) @@ -342,3 +355,59 @@ storiesOf('app/Overview', module) /> ); }); + +const coreAlertsThrowsError = ({ + ...core, + http: { + ...core.http, + get: async () => { + throw new Error('Error fetching Alerts data'); + }, + }, +} as unknown) as AppMountContext['core']; +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('with error', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: async () => { + throw new Error('Error fetching APM data'); + }, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: async () => { + throw new Error('Error fetching Logs data'); + }, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: async () => { + throw new Error('Error fetching Metric data'); + }, + hasData: () => Promise.resolve(true), + }); + registerDataHandler({ + appName: 'uptime', + fetchData: async () => { + throw new Error('Error fetching Uptime data'); + }, + hasData: () => Promise.resolve(true), + }); + return ( + + ); + }); From 90acb10eb97c8e92cc785f4fd1b094ba34a89f20 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 10:36:10 +0200 Subject: [PATCH 81/97] adding feedback button --- .../public/components/app/header/index.tsx | 36 +++++++++++++++---- .../components/app/layout/with_header.tsx | 9 ++++- .../public/pages/overview/index.tsx | 18 +++++----- .../pages/overview/overview.stories.tsx | 1 - 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx index 5b2bfbf36f0a7..74b9114650d39 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -4,12 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { + EuiBetaBadge, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import React from 'react'; import styled from 'styled-components'; -import { EuiButtonEmpty } from '@elastic/eui'; -import { EuiBetaBadge } from '@elastic/eui'; const Container = styled.div<{ color: string }>` background: ${(props) => props.color}; @@ -28,9 +34,15 @@ interface Props { color: string; showAddData?: boolean; restrictWidth?: number; + showGiveFeedback?: boolean; } -export const Header = ({ color, restrictWidth, showAddData = false }: Props) => { +export const Header = ({ + color, + restrictWidth, + showAddData = false, + showGiveFeedback = false, +}: Props) => { return ( @@ -52,8 +64,20 @@ export const Header = ({ color, restrictWidth, showAddData = false }: Props) => + {showGiveFeedback && ( + + + {i18n.translate('xpack.observability.home.feedback', { + defaultMessage: 'Give us feedback', + })} + + + )} {showAddData && ( - + {/* TODO: caue: what is the URL here? */} {i18n.translate('xpack.observability.home.addData', { defaultMessage: 'Add data' })} diff --git a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx index 06bf318206aa8..27b25f0056055 100644 --- a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx +++ b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx @@ -29,6 +29,7 @@ interface Props { children?: React.ReactNode; restrictWidth?: number; showAddData?: boolean; + showGiveFeedback?: boolean; } export const WithHeaderLayout = ({ @@ -37,9 +38,15 @@ export const WithHeaderLayout = ({ children, restrictWidth, showAddData, + showGiveFeedback, }: Props) => ( -
+
{children} diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index aaeb31d702a82..9498cafa27e5e 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -20,30 +20,29 @@ import { DatePicker, TimePickerTime } from '../../components/shared/data_picker' import { fetchHasData } from '../../data_handler'; import { useFetcher } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; +import { usePluginContext } from '../../hooks/use_plugin_context'; import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { emptySections } from './emptySection'; -import { usePluginContext } from '../../hooks/use_plugin_context'; interface Props { routeParams: RouteParams<'/overview'>; } export const OverviewPage = ({ routeParams }: Props) => { - const getAlerts = async () => { - return await core.http.get('/api/alerts/_find', { + const { core } = usePluginContext(); + const result = useFetcher(() => fetchHasData(), []); + const hasData = result.data; + + const alerts = useFetcher(() => { + return core.http.get('/api/alerts/_find', { query: { page: 1, per_page: 10, }, }); - }; - const { core } = usePluginContext(); - const result = useFetcher(() => fetchHasData(), []); - const hasData = result.data; - - const alerts = useFetcher(() => getAlerts(), []); + }, []); const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); @@ -76,6 +75,7 @@ export const OverviewPage = ({ routeParams }: Props) => { headerColor={theme.eui.euiColorEmptyShade} bodyColor={theme.eui.euiPageBackgroundColor} showAddData + showGiveFeedback > diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 17543aa620d31..4f08ae6ee3894 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -213,7 +213,6 @@ storiesOf('app/Overview', module) )) .add('logs, metrics, APM and alerts', () => { - // TODO: add alert here unregisterAll(); registerDataHandler({ appName: 'infra_logs', From 518f092451365a4485dc92df350231c6be148fc0 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 16:11:58 +0200 Subject: [PATCH 82/97] addressing PR comments --- .../components/app/chart_container/index.tsx | 59 +++--- .../components/app/section/apm/index.tsx | 86 ++++----- .../app/section/error_panel/index.tsx | 2 +- .../public/components/app/section/index.tsx | 22 +-- .../components/app/section/logs/index.tsx | 158 ++++++++-------- .../components/app/section/metrics/index.tsx | 51 +++--- .../components/app/section/uptime/index.tsx | 70 ++++--- .../components/app/styled_stat/index.tsx | 19 +- .../public/pages/overview/emptySection.ts | 15 ++ .../public/pages/overview/index.tsx | 86 ++++++--- .../pages/overview/loading_observability.tsx | 53 ++++++ .../public/pages/overview/mock/apm.mock.ts | 5 - .../pages/overview/mock/metrics.mock.ts | 24 +-- .../pages/overview/overview.stories.tsx | 172 +++++++++++++++--- .../typings/fetch_overview_data/index.ts | 8 +- .../public/typings/section/index.ts | 2 +- 16 files changed, 513 insertions(+), 319 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/overview/loading_observability.tsx diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx index 86bada1b44d2a..4e159cebff1b3 100644 --- a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx @@ -3,45 +3,40 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { Chart } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart } from '@elastic/eui'; import { EuiLoadingChartSize } from '@elastic/eui/src/components/loading/loading_chart'; -import React, { useContext } from 'react'; -import { ThemeContext } from 'styled-components'; +import React from 'react'; interface Props { - isLoading: boolean; - height: number; + isInitialLoad: boolean; + height?: number; width?: number; iconSize?: EuiLoadingChartSize; children: React.ReactNode; } -export const ChartContainer = ({ isLoading, height, children, iconSize = 'xl', width }: Props) => { - const theme = useContext(ThemeContext); - const style = { - height, - marginTop: `-${height}px`, - marginBottom: 0, - width, - opacity: 0.3, - backgroundColor: theme.eui.euiColorFullShade, - borderRadius: '4px', - }; - return ( - <> -
{isLoading === false && children}
- {isLoading === true && ( - - - - - - )} - - ); +const CHART_HEIGHT = 170; + +export const ChartContainer = ({ + isInitialLoad, + children, + iconSize = 'xl', + height = CHART_HEIGHT, +}: Props) => { + if (isInitialLoad) { + return ( +
+ +
+ ); + } + return {children}; }; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index deed3471991cc..47d677a75fce5 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -4,17 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Axis, - BarSeries, - Chart, - niceTimeFormatter, - Position, - ScaleType, - Settings, -} from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; +import { Axis, BarSeries, niceTimeFormatter, Position, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; @@ -56,64 +49,63 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { const isLoading = status === FETCH_STATUS.LOADING; - const transactionsColor = series?.transactions.color || theme.euiColorVis1; + const transactionsColor = theme.eui.euiColorVis1; return ( - - - - onBrushEnd({ x, history })} - theme={useChartTheme()} - showLegend={false} - xDomain={{ min, max }} - /> - {series?.transactions.coordinates && ( - <> - - `${numeral(value).format('0.00a')} tpm`} - /> - - - )} - + + onBrushEnd({ x, history })} + theme={useChartTheme()} + showLegend={false} + xDomain={{ min, max }} + /> + {series?.transactions.coordinates && ( + <> + + `${numeral(value).format('0.00a')} tpm`} + /> + + + )} ); diff --git a/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx b/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx index eaa1f42e68e07..8f0781b8f0269 100644 --- a/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; export const ErrorPanel = () => { return ( - + {i18n.translate('xpack.observability.section.errorPanel', { diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx index 3de8348909812..3556e8c01ab30 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -18,14 +18,7 @@ interface Props { appLinkName?: string; } -export const SectionContainer = ({ - title, - appLink, - children, - minHeight, - hasError, - appLinkName, -}: Props) => { +export const SectionContainer = ({ title, appLink, children, hasError, appLinkName }: Props) => { const { core } = usePluginContext(); return ( <> - + {hasError ? ( ) : ( diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index d5d2523c3b64e..ec6840f4f012c 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -4,18 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Axis, - BarSeries, - Chart, - niceTimeFormatter, - Position, - ScaleType, - Settings, -} from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { Axis, BarSeries, niceTimeFormatter, Position, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; import moment from 'moment'; import React, { Fragment } from 'react'; import { useHistory } from 'react-router-dom'; @@ -35,6 +27,21 @@ interface Props { bucketSize?: string; } +function getColorPerItem(series?: LogsFetchDataResponse['series']) { + if (!series) { + return {}; + } + const availableColors = euiPaletteColorBlind({ + rotations: Math.ceil(Object.keys(series).length / 10), + }); + const colorsPerItem = Object.keys(series).reduce((acc: Record, key, index) => { + acc[key] = availableColors[index]; + return acc; + }, {}); + + return colorsPerItem; +} + export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const history = useHistory(); @@ -49,99 +56,84 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { const formatter = niceTimeFormatter([min, max]); - const { title = 'Logs', appLink, stats, series } = data || {}; + const { title, appLink, stats, series } = data || {}; - const availableColors = euiPaletteColorBlind({ - rotations: data ? Math.ceil(Object.keys(data.series).length / 10) : 1, - }); - const colorsPerItem = stats - ? Object.keys(stats).reduce((acc: Record, key, index) => { - acc[key] = availableColors[index]; - return acc; - }, {}) - : {}; + const colorsPerItem = getColorPerItem(series); const isLoading = status === FETCH_STATUS.LOADING; return ( - -

- {i18n.translate('xpack.observability.overview.logs.subtitle', { - defaultMessage: 'Log rate', - })} -

-
- - {stats && + {!stats || isEmpty(stats) ? ( + + + + ) : ( Object.keys(stats).map((key) => { - const stat = stats[key as keyof LogsFetchDataResponse['stats']]; + const stat = stats[key]; return ( - + ); - })} + }) + )} - - - onBrushEnd({ x, history })} - theme={useChartTheme()} - showLegend - legendPosition={Position.Right} - xDomain={{ min, max }} - showLegendExtra - /> - {series && - Object.keys(series).map((key) => { - const serie = series[key]; - const chartData = serie.coordinates.map((coordinate) => ({ - ...coordinate, - g: serie.label, - })); - return ( - - - - numeral(d).format('0a')} - /> - - ); - })} - + + onBrushEnd({ x, history })} + theme={useChartTheme()} + showLegend + legendPosition={Position.Right} + xDomain={{ min, max }} + showLegendExtra + /> + {series && + Object.keys(series).map((key) => { + const serie = series[key]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); + return ( + + + + numeral(d).format('0a')} + /> + + ); + })}
); diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index dd8acc15b368b..6276e1ba1baca 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AreaSeries, Chart, ScaleType, Settings } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiStat } from '@elastic/eui'; +import { AreaSeries, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer } from '@elastic/eui'; import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import styled, { ThemeContext } from 'styled-components'; import { SectionContainer } from '../'; @@ -57,10 +58,10 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { const { title = 'Metrics', appLink, stats, series } = data || {}; - const cpuColor = stats?.cpu.color || theme.eui.euiColorVis7; - const memoryColor = stats?.cpu.color || theme.eui.euiColorVis0; - const inboundTrafficColor = series?.inboundTraffic.color || theme.eui.euiColorVis3; - const outboundTrafficColor = series?.outboundTraffic.color || theme.eui.euiColorVis2; + const cpuColor = theme.eui.euiColorVis7; + const memoryColor = theme.eui.euiColorVis0; + const inboundTrafficColor = theme.eui.euiColorVis3; + const outboundTrafficColor = theme.eui.euiColorVis2; return ( { > - @@ -100,8 +103,9 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { @@ -114,8 +118,9 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { @@ -129,8 +134,9 @@ export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { @@ -156,14 +162,11 @@ const AreaChart = ({ color: string; }) => { const chartTheme = useChartTheme(); - if (!serie) { - return null; - } return ( - - - + + + {serie && ( - + )} ); }; diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 60a7ca3d9d70b..412fec41c654d 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -7,15 +7,15 @@ import { Axis, BarSeries, - Chart, niceTimeFormatter, Position, ScaleType, Settings, TickFormatter, } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; @@ -53,8 +53,8 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const { title = 'Uptime', appLink, stats, series } = data || {}; - const downColor = series?.down.color || theme.eui.euiColorVis2; - const upColor = series?.up.color || theme.eui.euiColorLightShade; + const downColor = theme.eui.euiColorVis2; + const upColor = theme.eui.euiColorLightShade; return ( { {/* Stats section */} - @@ -85,8 +87,9 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { @@ -94,23 +97,32 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { {/* Chart section */} - - - onBrushEnd({ x, history })} - theme={useChartTheme()} - showLegend={false} - legendPosition={Position.Right} - xDomain={{ min, max }} - /> - - - + + onBrushEnd({ x, history })} + theme={useChartTheme()} + showLegend={false} + legendPosition={Position.Right} + xDomain={{ min, max }} + /> + + ); @@ -118,11 +130,13 @@ export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { const UptimeBarSeries = ({ id, + label, series, color, ticktFormatter, }: { id: string; + label: string; series?: Series; color: string; ticktFormatter: TickFormatter; diff --git a/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx b/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx index a54993c5538bf..fe38df6484c29 100644 --- a/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx +++ b/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx @@ -5,12 +5,23 @@ */ import styled from 'styled-components'; import { EuiStat } from '@elastic/eui'; +import React from 'react'; +import { EuiStatProps } from '@elastic/eui/src/components/stat/stat'; -/** - * This component is needed until EuiStat supports custom colors - */ -export const StyledStat = styled(EuiStat)` +const Stat = styled(EuiStat)` .euiStat__title { color: ${(props) => props.color}; } `; + +interface Props extends Partial { + children?: React.ReactNode; + color?: string; +} + +const EMPTY_VALUE = '--'; + +export const StyledStat = (props: Props) => { + const { description = EMPTY_VALUE, title = EMPTY_VALUE, ...rest } = props; + return ; +}; diff --git a/x-pack/plugins/observability/public/pages/overview/emptySection.ts b/x-pack/plugins/observability/public/pages/overview/emptySection.ts index 16a21b3ffd7ec..616533ec50310 100644 --- a/x-pack/plugins/observability/public/pages/overview/emptySection.ts +++ b/x-pack/plugins/observability/public/pages/overview/emptySection.ts @@ -67,4 +67,19 @@ export const emptySections: ISection[] = [ }), href: 'https://www.elastic.co', }, + { + id: 'alert', + title: i18n.translate('xpack.observability.emptySection.apps.alert.title', { + defaultMessage: 'No alerts found.', + }), + icon: 'watchesApp', + description: i18n.translate('xpack.observability.emptySection.apps.alert.description', { + defaultMessage: + '5003 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.alert.link', { + defaultMessage: 'Create alert', + }), + href: 'https://www.elastic.co', + }, ]; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 9498cafa27e5e..55ab10b046f08 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -9,7 +9,6 @@ import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import { EmptySection } from '../../components/app/empty_section'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; -import { News } from '../../components/app/news'; import { Resources } from '../../components/app/resources'; import { AlertsSection } from '../../components/app/section/alerts'; import { APMSection } from '../../components/app/section/apm'; @@ -18,22 +17,31 @@ import { MetricsSection } from '../../components/app/section/metrics'; import { UptimeSection } from '../../components/app/section/uptime'; import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; import { fetchHasData } from '../../data_handler'; -import { useFetcher } from '../../hooks/use_fetcher'; +import { useFetcher, FETCH_STATUS } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { RouteParams } from '../../routes'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; import { emptySections } from './emptySection'; +import { LoadingObservability } from './loading_observability'; interface Props { routeParams: RouteParams<'/overview'>; } +function calculatetBucketSize({ startTime, endTime }: { startTime?: string; endTime?: string }) { + if (startTime && endTime) { + return getBucketSize({ + start: moment.utc(startTime).valueOf(), + end: moment.utc(endTime).valueOf(), + minInterval: '60s', + }); + } +} + export const OverviewPage = ({ routeParams }: Props) => { const { core } = usePluginContext(); - const result = useFetcher(() => fetchHasData(), []); - const hasData = result.data; const alerts = useFetcher(() => { return core.http.get('/api/alerts/_find', { @@ -43,10 +51,18 @@ export const OverviewPage = ({ routeParams }: Props) => { }, }); }, []); + const alertData = alerts?.data?.data; const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); + const result = useFetcher(() => fetchHasData(), []); + const hasData = result.data; + + if (!hasData) { + return ; + } + const { rangeFrom = timePickerTime.from, rangeTo = timePickerTime.to, @@ -56,19 +72,17 @@ export const OverviewPage = ({ routeParams }: Props) => { const startTime = getParsedDate(rangeFrom); const endTime = getParsedDate(rangeTo, { roundUp: true }); - const bucketSize = - startTime && endTime - ? getBucketSize({ - start: moment.utc(startTime).valueOf(), - end: moment.utc(endTime).valueOf(), - minInterval: '60s', - }) - : undefined; - - const appEmptySections = emptySections.filter(({ id }) => hasData && !hasData[id]); - const showSections = hasData - ? Object.values(hasData).some((hasPluginData) => hasPluginData) - : false; + const bucketSize = calculatetBucketSize({ startTime, endTime }); + + const appEmptySections = emptySections.filter(({ id }) => { + if (id === 'alert') { + return alerts.status !== FETCH_STATUS.FAILURE && !alertData?.length; + } + return !hasData[id]; + }); + + // Hides the data section when all 'hasData' is false or undefined + const showDataSections = Object.values(hasData).some((hasPluginData) => hasPluginData); return ( { /> - + + - {showSections && ( + {/* Data sections */} + {showDataSections && ( - {hasData?.infra_logs && ( + {hasData.infra_logs && ( { /> )} - {hasData?.infra_metrics && ( + {hasData.infra_metrics && ( { /> )} - {hasData?.apm && ( + {hasData.apm && ( { /> )} - {hasData?.uptime && ( + {hasData.uptime && ( { )} )} + + {/* Empty sections */} {!!appEmptySections.length && ( - + 2 ? 2 : 1 + } + gutterSize="s" + > {appEmptySections.map((app) => { return ( { )} - {alerts?.data?.data && ( + {/* Alert section */} + {alertData && ( - + )} + {/* Resources section */} - - - diff --git a/x-pack/plugins/observability/public/pages/overview/loading_observability.tsx b/x-pack/plugins/observability/public/pages/overview/loading_observability.tsx new file mode 100644 index 0000000000000..90e3104443e6b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/loading_observability.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useContext } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { WithHeaderLayout } from '../../components/app/layout/with_header'; + +const CentralizedFlexGroup = styled(EuiFlexGroup)` + justify-content: center; + align-items: center; + // place the element in the center of the page + min-height: calc(100vh - ${(props) => props.theme.eui.euiHeaderChildSize}); +`; + +export const LoadingObservability = () => { + const theme = useContext(ThemeContext); + + return ( + + + + + + + + + + + {i18n.translate('xpack.observability.overview.loadingObservability', { + defaultMessage: 'Loading Observability', + })} + + + + + + + + ); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts index a8c272887a9ac..f82b230e48ad3 100644 --- a/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts @@ -617,20 +617,15 @@ export const emptyResponse: ApmFetchDataResponse = { stats: { services: { type: 'number', - label: 'Services', value: 0, }, transactions: { type: 'number', - label: 'Transactions', value: 0, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', - color: '#6092c0', coordinates: [], }, }, diff --git a/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts index 81077e4905545..37233b4f6342c 100644 --- a/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts @@ -14,15 +14,14 @@ const response: MetricsFetchDataResponse = { title: 'Metrics', appLink: '/app/apm', stats: { - hosts: { label: 'Hosts', value: 11, type: 'number' }, - cpu: { label: 'CPU usage', value: 0.8, type: 'percent' }, - memory: { label: 'Memory Usage', value: 0.362, type: 'percent' }, - inboundTraffic: { label: 'Inbount traffic', value: 1024, type: 'bytesPerSecond' }, - outboundTraffic: { label: 'Outbount traffic', value: 1024, type: 'bytesPerSecond' }, + hosts: { value: 11, type: 'number' }, + cpu: { value: 0.8, type: 'percent' }, + memory: { value: 0.362, type: 'percent' }, + inboundTraffic: { value: 1024, type: 'bytesPerSecond' }, + outboundTraffic: { value: 1024, type: 'bytesPerSecond' }, }, series: { outboundTraffic: { - label: 'Outbount traffic', coordinates: [ { x: 1589805437549, @@ -67,7 +66,6 @@ const response: MetricsFetchDataResponse = { ], }, inboundTraffic: { - label: 'Inbound traffic', coordinates: [ { x: 1589805437549, @@ -118,19 +116,17 @@ export const emptyResponse: MetricsFetchDataResponse = { title: 'Metrics', appLink: '/app/apm', stats: { - hosts: { label: 'Hosts', value: 0, type: 'number' }, - cpu: { label: 'CPU usage', value: 0, type: 'percent' }, - memory: { label: 'Memory Usage', value: 0, type: 'percent' }, - inboundTraffic: { label: 'Inbount traffic', value: 0, type: 'bytesPerSecond' }, - outboundTraffic: { label: 'Outbount traffic', value: 0, type: 'bytesPerSecond' }, + hosts: { value: 0, type: 'number' }, + cpu: { value: 0, type: 'percent' }, + memory: { value: 0, type: 'percent' }, + inboundTraffic: { value: 0, type: 'bytesPerSecond' }, + outboundTraffic: { value: 0, type: 'bytesPerSecond' }, }, series: { outboundTraffic: { - label: 'Outbount traffic', coordinates: [], }, inboundTraffic: { - label: 'Inbound traffic', coordinates: [], }, }, diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 4f08ae6ee3894..b88614b22e81a 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -119,6 +119,27 @@ storiesOf('app/Overview', module) )) .add('Empty state', () => { unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: async () => false, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => false, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => false, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: async () => false, + }); + return ; }); @@ -135,7 +156,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'infra_logs', fetchData: fetchLogsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_logs', fetchData: fetchLogsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'uptime', fetchData: fetchUptimeData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_logs', fetchData: fetchLogsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: fetchMetricsData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'uptime', fetchData: fetchUptimeData, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( emptyAPMResponse, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_logs', fetchData: async () => emptyLogsResponse, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: async () => emptyMetricsResponse, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'uptime', fetchData: async () => emptyUptimeResponse, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); return ( )) - .add('with error', () => { + .add('fetch data with error', () => { unregisterAll(); registerDataHandler({ appName: 'apm', fetchData: async () => { throw new Error('Error fetching APM data'); }, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_logs', fetchData: async () => { throw new Error('Error fetching Logs data'); }, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'infra_metrics', fetchData: async () => { throw new Error('Error fetching Metric data'); }, - hasData: () => Promise.resolve(true), + hasData: async () => true, }); registerDataHandler({ appName: 'uptime', fetchData: async () => { throw new Error('Error fetching Uptime data'); }, - hasData: () => Promise.resolve(true), + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('hasData with error and alerts', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + return ( + + ); + }); +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('hasData with error', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, }); return ( ; - series: Record; + stats: Record; + series: Record; } export interface MetricsFetchDataResponse extends FetchDataResponse { diff --git a/x-pack/plugins/observability/public/typings/section/index.ts b/x-pack/plugins/observability/public/typings/section/index.ts index 7e6eec771af52..f336b6b981687 100644 --- a/x-pack/plugins/observability/public/typings/section/index.ts +++ b/x-pack/plugins/observability/public/typings/section/index.ts @@ -7,7 +7,7 @@ import { ObservabilityApp } from '../../../typings/common'; export interface ISection { - id: ObservabilityApp; + id: ObservabilityApp | 'alert'; title: string; icon: string; description: string; From 0b9bcb251f32c531ed6295b0221993de214258e0 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 16:16:43 +0200 Subject: [PATCH 83/97] chamging plugins return data --- x-pack/plugins/apm/public/plugin.ts | 3 +- .../services/rest/observability_dashboard.ts | 28 ++++--------------- .../infra/public/metrics_overview_fetchers.ts | 21 -------------- .../public/apps/uptime_overview_fetcher.ts | 5 ---- 4 files changed, 6 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index d24cb29eaf24f..67ee806aee927 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; import { lazy } from 'react'; -import { euiThemeVars as theme } from '@kbn/ui-shared-deps/theme'; import { ConfigSchema } from '.'; import { ObservabilityPluginSetup } from '../../observability/public'; import { @@ -82,7 +81,7 @@ export class ApmPlugin implements Plugin { plugins.observability.dashboard.register({ appName: 'apm', fetchData: async (params) => { - return fetchLandingPageData(params, { theme }); + return fetchLandingPageData(params); }, hasData, }); diff --git a/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts b/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts index 79ccf8dbd6f9b..409cec8b9ce10 100644 --- a/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts +++ b/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts @@ -6,21 +6,17 @@ import { i18n } from '@kbn/i18n'; import { mean } from 'lodash'; -import { Theme } from '@kbn/ui-shared-deps/theme'; import { ApmFetchDataResponse, FetchDataParams, } from '../../../../observability/public'; import { callApmApi } from './createCallApmApi'; -interface Options { - theme: Theme; -} - -export const fetchLandingPageData = async ( - { startTime, endTime, bucketSize }: FetchDataParams, - { theme }: Options -): Promise => { +export const fetchLandingPageData = async ({ + startTime, + endTime, + bucketSize, +}: FetchDataParams): Promise => { const data = await callApmApi({ pathname: '/api/apm/observability_dashboard', params: { query: { start: startTime, end: endTime, bucketSize } }, @@ -36,34 +32,20 @@ export const fetchLandingPageData = async ( stats: { services: { type: 'number', - label: i18n.translate( - 'xpack.apm.observabilityDashboard.stats.services', - { defaultMessage: 'Services' } - ), value: serviceCount, }, transactions: { type: 'number', - label: i18n.translate( - 'xpack.apm.observabilityDashboard.stats.transactions', - { defaultMessage: 'Transactions' } - ), value: mean( transactionCoordinates .map(({ y }) => y) .filter((y) => y && isFinite(y)) ) || 0, - color: theme.euiColorVis1, }, }, series: { transactions: { - label: i18n.translate( - 'xpack.apm.observabilityDashboard.chart.transactions', - { defaultMessage: 'Transactions' } - ), - color: theme.euiColorVis1, coordinates: transactionCoordinates, }, }, diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts index d10ad5dda5320..931eb642595a1 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts @@ -101,14 +101,6 @@ export const createMetricsFetchData = ( body: JSON.stringify(snapshotRequest), }); - const inboundLabel = i18n.translate('xpack.infra.observabilityHomepage.metrics.rxLabel', { - defaultMessage: 'Inbound traffic', - }); - - const outboundLabel = i18n.translate('xpack.infra.observabilityHomepage.metrics.txLabel', { - defaultMessage: 'Outbound traffic', - }); - return { title: i18n.translate('xpack.infra.observabilityHomepage.metrics.title', { defaultMessage: 'Metrics', @@ -117,43 +109,30 @@ export const createMetricsFetchData = ( stats: { hosts: { type: 'number', - label: i18n.translate('xpack.infra.observabilityHomepage.metrics.hostsLabel', { - defaultMessage: 'Hosts', - }), value: results.nodes.length, }, cpu: { type: 'percent', - label: i18n.translate('xpack.infra.observabilityHomepage.metrics.cpuLabel', { - defaultMessage: 'CPU usage', - }), value: combineNodesBy('cpu', results.nodes, average), }, memory: { type: 'percent', - label: i18n.translate('xpack.infra.observabilityHomepage.metrics.memoryLabel', { - defaultMessage: 'Memory usage', - }), value: combineNodesBy('memory', results.nodes, average), }, inboundTraffic: { type: 'bytesPerSecond', - label: inboundLabel, value: combineNodesBy('rx', results.nodes, average), }, outboundTraffic: { type: 'bytesPerSecond', - label: outboundLabel, value: combineNodesBy('tx', results.nodes, average), }, }, series: { inboundTraffic: { - label: inboundLabel, coordinates: combineNodeTimeseriesBy('rx', results.nodes, average), }, outboundTraffic: { - label: outboundLabel, coordinates: combineNodeTimeseriesBy('tx', results.nodes, average), }, }, diff --git a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts index bede391537ec5..89720b275c63d 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts +++ b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts @@ -29,29 +29,24 @@ export async function fetchUptimeOverviewData({ stats: { monitors: { type: 'number', - label: 'Monitors', value: snapshot.total, }, up: { type: 'number', - label: 'Up', value: snapshot.up, }, down: { type: 'number', - label: 'Down', value: snapshot.down, }, }, series: { up: { - label: 'Up', coordinates: pings.histogram.map((p) => { return { x: p.x!, y: p.upCount || 0 }; }), }, down: { - label: 'Down', coordinates: pings.histogram.map((p) => { return { x: p.x!, y: p.downCount || 0 }; }), From 2b2be647a9dd0176bd8cb46e0ddb5b6933b9abaf Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 16:59:50 +0200 Subject: [PATCH 84/97] fixing kibana app navigation --- x-pack/plugins/observability/public/application/index.tsx | 5 ++++- .../public/components/app/section/apm/index.tsx | 6 +++--- .../public/components/app/section/uptime/index.tsx | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 4ed2ff20b78be..5bc8d96656ed4 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -8,6 +8,7 @@ import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { PluginContext } from '../context/plugin_context'; @@ -54,7 +55,9 @@ export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { - + + + diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 47d677a75fce5..697d4adfa0b75 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -26,7 +26,7 @@ interface Props { bucketSize?: string; } -function formatTransactionValue(value?: number) { +function formatTpm(value?: number) { return numeral(value).format('0.00a'); } @@ -69,7 +69,7 @@ export const APMSection = ({ startTime, endTime, bucketSize }: Props) => {
{ id="y-axis" position={Position.Left} showGridLines - tickFormat={(value) => `${numeral(value).format('0.00a')} tpm`} + tickFormat={(value) => `${formatTpm(value)} tpm`} /> diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 412fec41c654d..1f8ca6e61f132 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -146,7 +146,7 @@ const UptimeBarSeries = ({ } const chartData = series.coordinates.map((coordinate) => ({ ...coordinate, - g: series.label, + g: label, })); return ( <> From ea5baad2e34a7942522eaebc74c4bc7156605070 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 17:27:24 +0200 Subject: [PATCH 85/97] fixing unit test --- .../app/chart_container/index.test.tsx | 10 +++--- .../components/app/chart_container/index.tsx | 3 +- .../app/empty_section/index.test.tsx | 17 +++------ .../components/app/header/index.test.tsx | 15 ++------ .../public/components/app/news/index.test.tsx | 8 ++--- .../components/app/section/apm/index.test.tsx | 35 ++++++++----------- .../components/app/section/index.test.tsx | 31 +++++++++------- .../public/utils/test_helper.tsx | 26 ++++++++++++++ 8 files changed, 77 insertions(+), 68 deletions(-) create mode 100644 x-pack/plugins/observability/public/utils/test_helper.tsx diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx index 37fccd3903e8d..d09d535a49340 100644 --- a/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx @@ -3,25 +3,27 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { queryAllByTestId, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import React from 'react'; import { ChartContainer } from './'; describe('chart container', () => { it('shows loading indicator', () => { const component = render( - +
My amazing component
); expect(component.getByTestId('loading')).toBeInTheDocument(); + expect(component.queryByText('My amazing component')).not.toBeInTheDocument(); }); it("doesn't show loading indicator", () => { const component = render( - +
My amazing component
); - expect(queryAllByTestId(component.container, 'loading')).toEqual([]); + expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + expect(component.getByText('My amazing component')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx index 4e159cebff1b3..2a0c25773eae5 100644 --- a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Chart } from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart } from '@elastic/eui'; +import { EuiLoadingChart } from '@elastic/eui'; import { EuiLoadingChartSize } from '@elastic/eui/src/components/loading/loading_chart'; import React from 'react'; @@ -27,6 +27,7 @@ export const ChartContainer = ({ if (isInitialLoad) { return (
{ it('renders without action button', () => { @@ -17,11 +16,7 @@ describe('EmptySection', () => { icon: 'logoAPM', description: 'foo bar', }; - const { getByText, queryAllByText } = render( - - - - ); + const { getByText, queryAllByText } = render(); expect(getByText('APM')).toBeInTheDocument(); expect(getByText('foo bar')).toBeInTheDocument(); @@ -36,11 +31,7 @@ describe('EmptySection', () => { linkTitle: 'install agent', href: 'https://www.elastic.co', }; - const { getByText, getByTestId } = render( - - - - ); + const { getByText, getByTestId } = render(); expect(getByText('APM')).toBeInTheDocument(); expect(getByText('foo bar')).toBeInTheDocument(); diff --git a/x-pack/plugins/observability/public/components/app/header/index.test.tsx b/x-pack/plugins/observability/public/components/app/header/index.test.tsx index bb56058bf7697..59b6fbe9caf7a 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.test.tsx @@ -4,27 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { render } from '../../../utils/test_helper'; import { Header } from './'; -import { render } from '@testing-library/react'; -import { EuiThemeProvider } from '../../../typings'; describe('Header', () => { it('renders without add data button', () => { - const { getByText, queryAllByText, getByTestId } = render( - -
- - ); + const { getByText, queryAllByText, getByTestId } = render(
); expect(getByTestId('observability-logo')).toBeInTheDocument(); expect(getByText('Observability')).toBeInTheDocument(); expect(queryAllByText('Add data')).toEqual([]); }); it('renders with add data button', () => { - const { getByText, getByTestId } = render( - -
- - ); + const { getByText, getByTestId } = render(
); expect(getByTestId('observability-logo')).toBeInTheDocument(); expect(getByText('Observability')).toBeInTheDocument(); expect(getByText('Add data')).toBeInTheDocument(); diff --git a/x-pack/plugins/observability/public/components/app/news/index.test.tsx b/x-pack/plugins/observability/public/components/app/news/index.test.tsx index 1ed9212f6c6c0..09c84d2c2ebbc 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.test.tsx @@ -3,18 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { render } from '@testing-library/react'; import React from 'react'; import { News } from './'; import { EuiThemeProvider } from '../../../typings'; +import { render } from '../../../utils/test_helper'; describe('News', () => { it('renders resources with all elements', () => { - const { getByText, getAllByText } = render( - - - - ); + const { getByText, getAllByText } = render(); expect(getByText("What's new")).toBeInTheDocument(); expect(getAllByText('Read full story')).not.toEqual([]); }); diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index 6ef10dbc4ef61..d4b8236e0ef49 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -3,12 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { APMSection } from './'; -import { response } from './mock_data/apm.mock'; -import { render } from '@testing-library/react'; -import { EuiThemeProvider } from '../../../../typings'; import React from 'react'; import * as fetcherHook from '../../../../hooks/use_fetcher'; +import { render } from '../../../../utils/test_helper'; +import { APMSection } from './'; +import { response } from './mock_data/apm.mock'; describe('APMSection', () => { it('renders with transaction series and stats', () => { @@ -18,19 +17,17 @@ describe('APMSection', () => { refetch: jest.fn(), }); const { getByText, queryAllByTestId } = render( - - - + ); expect(getByText('APM')).toBeInTheDocument(); expect(getByText('View in app')).toBeInTheDocument(); expect(getByText('Services 11')).toBeInTheDocument(); - expect(getByText('Transactions 312k')).toBeInTheDocument(); + expect(getByText('Transactions per minute 312.00k')).toBeInTheDocument(); expect(queryAllByTestId('loading')).toEqual([]); }); it('shows loading state', () => { @@ -40,19 +37,17 @@ describe('APMSection', () => { refetch: jest.fn(), }); const { getByText, queryAllByText, getByTestId } = render( - - - + ); expect(getByText('APM')).toBeInTheDocument(); expect(getByTestId('loading')).toBeInTheDocument(); expect(queryAllByText('View in app')).toEqual([]); expect(queryAllByText('Services 11')).toEqual([]); - expect(queryAllByText('Transactions 312k')).toEqual([]); + expect(queryAllByText('Transactions per minute 312.00k')).toEqual([]); }); }); diff --git a/x-pack/plugins/observability/public/components/app/section/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/index.test.tsx index 8086135ce9834..49cb175d0c094 100644 --- a/x-pack/plugins/observability/public/components/app/section/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/index.test.tsx @@ -4,18 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { render } from '../../../utils/test_helper'; import { SectionContainer } from './'; -import { render } from '@testing-library/react'; -import { EuiThemeProvider } from '../../../typings'; describe('SectionContainer', () => { it('renders section without app link', () => { const component = render( - - -
I am a very nice component
-
-
+ +
I am a very nice component
+
); expect(component.getByText('I am a very nice component')).toBeInTheDocument(); expect(component.getByText('Foo')).toBeInTheDocument(); @@ -23,14 +20,24 @@ describe('SectionContainer', () => { }); it('renders section with app link', () => { const component = render( - - -
I am a very nice component
-
-
+ +
I am a very nice component
+
); expect(component.getByText('I am a very nice component')).toBeInTheDocument(); expect(component.getByText('Foo')).toBeInTheDocument(); expect(component.getByText('View in app')).toBeInTheDocument(); }); + it('renders section with error', () => { + const component = render( + +
I am a very nice component
+
+ ); + expect(component.queryByText('I am a very nice component')).not.toBeInTheDocument(); + expect(component.getByText('Foo')).toBeInTheDocument(); + expect( + component.getByText('An error happened when trying to fetch data. Please try again') + ).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx new file mode 100644 index 0000000000000..2a290f2b24d6b --- /dev/null +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render as testLibRender } from '@testing-library/react'; +import { AppMountContext } from 'kibana/public'; +import { PluginContext } from '../context/plugin_context'; +import { EuiThemeProvider } from '../typings'; + +export const core = ({ + http: { + basePath: { + prepend: jest.fn(), + }, + }, +} as unknown) as AppMountContext['core']; + +export const render = (component: React.ReactNode) => { + return testLibRender( + + {component} + + ); +}; From 4f4f751bd4b7a2fc44673e2886dc0dd2e2867604 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 17:34:22 +0200 Subject: [PATCH 86/97] fixing ts issues --- .../rest/observability.dashboard.test.ts | 40 +++++++------------ .../public/components/app/news/index.test.tsx | 3 +- .../app/section/apm/mock_data/apm.mock.ts | 7 +--- .../public/pages/overview/mock/apm.mock.ts | 5 --- .../public/pages/overview/mock/uptime.mock.ts | 10 ----- 5 files changed, 18 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts index a14d827eeaec5..09318d4f6d796 100644 --- a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts +++ b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts @@ -6,7 +6,6 @@ import { fetchLandingPageData, hasData } from './observability_dashboard'; import * as createCallApmApi from './createCallApmApi'; -import { euiThemeVars as theme } from '@kbn/ui-shared-deps/theme'; describe('Observability dashboard data', () => { const callApmApiMock = jest.spyOn(createCallApmApi, 'callApmApi'); @@ -38,14 +37,11 @@ describe('Observability dashboard data', () => { ], }) ); - const response = await fetchLandingPageData( - { - startTime: '1', - endTime: '2', - bucketSize: '3', - }, - { theme } - ); + const response = await fetchLandingPageData({ + startTime: '1', + endTime: '2', + bucketSize: '3', + }); expect(response).toEqual({ title: 'APM', appLink: '/app/apm', @@ -82,14 +78,11 @@ describe('Observability dashboard data', () => { transactionCoordinates: [], }) ); - const response = await fetchLandingPageData( - { - startTime: '1', - endTime: '2', - bucketSize: '3', - }, - { theme } - ); + const response = await fetchLandingPageData({ + startTime: '1', + endTime: '2', + bucketSize: '3', + }); expect(response).toEqual({ title: 'APM', appLink: '/app/apm', @@ -122,14 +115,11 @@ describe('Observability dashboard data', () => { transactionCoordinates: [{ x: 1 }, { x: 2 }, { x: 3 }], }) ); - const response = await fetchLandingPageData( - { - startTime: '1', - endTime: '2', - bucketSize: '3', - }, - { theme } - ); + const response = await fetchLandingPageData({ + startTime: '1', + endTime: '2', + bucketSize: '3', + }); expect(response).toEqual({ title: 'APM', appLink: '/app/apm', diff --git a/x-pack/plugins/observability/public/components/app/news/index.test.tsx b/x-pack/plugins/observability/public/components/app/news/index.test.tsx index 09c84d2c2ebbc..cae6b4aec0c62 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.test.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { News } from './'; -import { EuiThemeProvider } from '../../../typings'; import { render } from '../../../utils/test_helper'; +import { News } from './'; describe('News', () => { it('renders resources with all elements', () => { diff --git a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts index 14dfc009dff91..5857021b1537f 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts +++ b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts @@ -11,14 +11,11 @@ export const response: ApmFetchDataResponse = { appLink: '/app/apm', stats: { - services: { label: 'Services', value: 11, type: 'number' }, - transactions: { label: 'Transactions', value: 312000, type: 'number', color: 'euiColorVis1' }, + services: { value: 11, type: 'number' }, + transactions: { value: 312000, type: 'number' }, }, series: { transactions: { - label: 'Transactions', - - color: 'euiColorVis1', coordinates: [ { x: 1591365600000, y: 32 }, { x: 1591366200000, y: 43 }, diff --git a/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts index f82b230e48ad3..7303b78cc0132 100644 --- a/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts @@ -15,20 +15,15 @@ const response: ApmFetchDataResponse = { stats: { services: { type: 'number', - label: 'Services', value: 7, }, transactions: { type: 'number', - label: 'Transactions', value: 125808, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', - color: '#6092c0', coordinates: [ { x: 1593295200000, diff --git a/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts index 2d5c744c52998..ab5874f8bfcd4 100644 --- a/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts @@ -15,23 +15,19 @@ const response: UptimeFetchDataResponse = { stats: { monitors: { type: 'number', - label: 'Monitors', value: 26, }, up: { type: 'number', - label: 'Up', value: 20, }, down: { type: 'number', - label: 'Down', value: 6, }, }, series: { up: { - label: 'Up', coordinates: [ { x: 1593295200000, @@ -612,7 +608,6 @@ const response: UptimeFetchDataResponse = { ], }, down: { - label: 'Down', coordinates: [ { x: 1593295200000, @@ -1201,27 +1196,22 @@ export const emptyResponse: UptimeFetchDataResponse = { stats: { monitors: { type: 'number', - label: 'Monitors', value: 0, }, up: { type: 'number', - label: 'Up', value: 0, }, down: { type: 'number', - label: 'Down', value: 0, }, }, series: { up: { - label: 'Up', coordinates: [], }, down: { - label: 'Down', coordinates: [], }, }, From fc1f42c18bf92f25e7c3df3600035c34a70f64ea Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 18:03:20 +0200 Subject: [PATCH 87/97] addressing PR comments --- .../public/components/app/header/index.tsx | 8 ++++++-- .../public/components/app/news/index.tsx | 8 ++++---- .../public/components/app/resources/index.test.tsx | 2 +- .../public/components/app/resources/index.tsx | 8 +++----- .../public/components/app/section/logs/index.tsx | 11 +++++++++++ .../public/pages/overview/emptySection.ts | 2 +- .../public/utils/get_bucket_size/index.ts | 1 - 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx index 74b9114650d39..b67a62b4cba90 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -16,6 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; const Container = styled.div<{ color: string }>` background: ${(props) => props.color}; @@ -43,6 +44,7 @@ export const Header = ({ showAddData = false, showGiveFeedback = false, }: Props) => { + const { core } = usePluginContext(); return ( @@ -78,8 +80,10 @@ export const Header = ({ )} {showAddData && ( - {/* TODO: caue: what is the URL here? */} - + {i18n.translate('xpack.observability.home.addData', { defaultMessage: 'Add data' })} diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx index f7e7d1cc2f1da..545d6d1201549 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -17,7 +17,7 @@ import { ThemeContext } from 'styled-components'; import './index.scss'; import { news as newsMockData } from './mock/news.mock.data'; -interface News { +interface NewsItem { title: string; description: string; link_url: string; @@ -25,7 +25,7 @@ interface News { } export const News = () => { - const news: News[] = newsMockData; + const newsItems: NewsItem[] = newsMockData; return ( @@ -37,7 +37,7 @@ export const News = () => { - {news.map((item, index) => ( + {newsItems.map((item, index) => ( @@ -49,7 +49,7 @@ export const News = () => { const limitString = (string: string, limit: number) => `${string.slice(0, limit)}${string.length > limit ? '...' : ''}`; -const NewsItem = ({ item }: { item: News }) => { +const NewsItem = ({ item }: { item: NewsItem }) => { const theme = useContext(ThemeContext); return ( diff --git a/x-pack/plugins/observability/public/components/app/resources/index.test.tsx b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx index 18a38722d6927..570aa3954424f 100644 --- a/x-pack/plugins/observability/public/components/app/resources/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx @@ -12,6 +12,6 @@ describe('Resources', () => { const { getByText } = render(); expect(getByText('Documentation')).toBeInTheDocument(); expect(getByText('Discuss forum')).toBeInTheDocument(); - expect(getByText('Training and webinars')).toBeInTheDocument(); + expect(getByText('Observability fundamentals')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/observability/public/components/app/resources/index.tsx b/x-pack/plugins/observability/public/components/app/resources/index.tsx index 48504768a39ec..c330c358d022a 100644 --- a/x-pack/plugins/observability/public/components/app/resources/index.tsx +++ b/x-pack/plugins/observability/public/components/app/resources/index.tsx @@ -13,8 +13,7 @@ const resources = [ label: i18n.translate('xpack.observability.resources.documentation', { defaultMessage: 'Documentation', }), - // TODO: caue what's the url? - href: 'https://www.elastic.co', + href: 'https://www.elastic.co/guide/en/observability/current/observability-ui.html', }, { iconType: 'editorComment', @@ -26,10 +25,9 @@ const resources = [ { iconType: 'training', label: i18n.translate('xpack.observability.resources.training', { - defaultMessage: 'Training and webinars', + defaultMessage: 'Observability fundamentals', }), - // TODO: caue what's the url? - href: 'https://www.elastic.co', + href: 'https://www.elastic.co/training/observability-fundamentals', }, ]; diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index ec6840f4f012c..f3ba2ef6fa83a 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -11,6 +11,9 @@ import { isEmpty } from 'lodash'; import moment from 'moment'; import React, { Fragment } from 'react'; import { useHistory } from 'react-router-dom'; +import { EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiSpacer } from '@elastic/eui'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { useChartTheme } from '../../../../hooks/use_chart_theme'; @@ -68,6 +71,14 @@ export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { appLink={appLink} hasError={status === FETCH_STATUS.FAILURE} > + +

+ {i18n.translate('xpack.observability.overview.logs.subtitle', { + defaultMessage: 'Logs rate per minute', + })} +

+
+ {!stats || isEmpty(stats) ? ( diff --git a/x-pack/plugins/observability/public/pages/overview/emptySection.ts b/x-pack/plugins/observability/public/pages/overview/emptySection.ts index 616533ec50310..2f5ad1bbd963a 100644 --- a/x-pack/plugins/observability/public/pages/overview/emptySection.ts +++ b/x-pack/plugins/observability/public/pages/overview/emptySection.ts @@ -75,7 +75,7 @@ export const emptySections: ISection[] = [ icon: 'watchesApp', description: i18n.translate('xpack.observability.emptySection.apps.alert.description', { defaultMessage: - '5003 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', + '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', }), linkTitle: i18n.translate('xpack.observability.emptySection.apps.alert.link', { defaultMessage: 'Create alert', diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts index e19f463c76656..5673b890adf33 100644 --- a/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts @@ -7,7 +7,6 @@ import moment from 'moment'; // @ts-ignore import { calculateAuto } from './calculate_auto'; -// @ts-ignore import { unitToSeconds } from './unit_to_seconds'; export function getBucketSize({ From 05077237da3b0c5ce4642af819d47d649ea53b23 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 18:10:34 +0200 Subject: [PATCH 88/97] using lodash truncate --- .../observability/public/components/app/news/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx index 545d6d1201549..084f1c248935e 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -15,6 +15,7 @@ import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { ThemeContext } from 'styled-components'; import './index.scss'; +import { truncate } from 'lodash'; import { news as newsMockData } from './mock/news.mock.data'; interface NewsItem { @@ -46,8 +47,7 @@ export const News = () => { ); }; -const limitString = (string: string, limit: number) => - `${string.slice(0, limit)}${string.length > limit ? '...' : ''}`; +const limitString = (string: string, limit: number) => truncate(string, { length: limit }); const NewsItem = ({ item }: { item: NewsItem }) => { const theme = useContext(ThemeContext); From 51c889b52a71e1287ab592618d19b87006f11187 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 18:18:20 +0200 Subject: [PATCH 89/97] adding comment --- x-pack/plugins/observability/public/hooks/use_url_params.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/observability/public/hooks/use_url_params.tsx b/x-pack/plugins/observability/public/hooks/use_url_params.tsx index acb05232ee47b..680a32fb49677 100644 --- a/x-pack/plugins/observability/public/hooks/use_url_params.tsx +++ b/x-pack/plugins/observability/public/hooks/use_url_params.tsx @@ -18,6 +18,11 @@ function getQueryParams(location: ReturnType) { return queryParams; } +/** + * Extracts query and path params from the url and validate it against the type defined in the route file. + * It removes any aditional item which is not declared in the type. + * @param params + */ export function useUrlParams(params: Params) { const location = useLocation(); const pathParams = useParams(); From 63a29fbe9fadd0f094554c0d57d6bb960e56b5af Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 19:25:39 +0200 Subject: [PATCH 90/97] updating public documentation --- ...-plugin-plugins-data-public.ui_settings.md | 55 ++++++++++--------- ...-plugin-plugins-data-server.ui_settings.md | 55 ++++++++++--------- src/plugins/data/public/public.api.md | 55 ++++++++++--------- src/plugins/data/server/server.api.md | 55 ++++++++++--------- 4 files changed, 112 insertions(+), 108 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md index a48f4920b3d26..e515c3513df6c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md @@ -8,32 +8,33 @@ ```typescript UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md index 855cfd11d00ea..e419b64cd43aa 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md @@ -8,32 +8,33 @@ ```typescript UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; } ``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 2b18584bcd781..c4854640fee08 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1942,33 +1942,34 @@ export interface TimeRange { // // @public (undocumented) export const UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index f029609cbf7ec..9768dba4c9c1b 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -766,33 +766,34 @@ export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string; // // @public (undocumented) export const UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; }; From 6ca417186fa4b01146285041c72bec55bed05fbe Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 21:03:57 +0200 Subject: [PATCH 91/97] fixing alerts request --- .../public/pages/overview/emptySection.ts | 85 ----------------- .../public/pages/overview/empty_section.ts | 90 ++++++++++++++++++ .../public/pages/overview/index.tsx | 23 ++--- .../services/get_observability_alerts.test.ts | 91 +++++++++++++++++++ .../services/get_observability_alerts.ts | 27 ++++++ 5 files changed, 217 insertions(+), 99 deletions(-) delete mode 100644 x-pack/plugins/observability/public/pages/overview/emptySection.ts create mode 100644 x-pack/plugins/observability/public/pages/overview/empty_section.ts create mode 100644 x-pack/plugins/observability/public/services/get_observability_alerts.test.ts create mode 100644 x-pack/plugins/observability/public/services/get_observability_alerts.ts diff --git a/x-pack/plugins/observability/public/pages/overview/emptySection.ts b/x-pack/plugins/observability/public/pages/overview/emptySection.ts deleted file mode 100644 index 2f5ad1bbd963a..0000000000000 --- a/x-pack/plugins/observability/public/pages/overview/emptySection.ts +++ /dev/null @@ -1,85 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { i18n } from '@kbn/i18n'; -import { ISection } from '../../typings/section'; - -export const emptySections: ISection[] = [ - { - id: 'infra_logs', - title: i18n.translate('xpack.observability.emptySection.apps.logs.title', { - defaultMessage: 'Logs', - }), - icon: 'logoLogging', - description: i18n.translate('xpack.observability.emptySection.apps.logs.description', { - defaultMessage: - 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', - }), - linkTitle: i18n.translate('xpack.observability.emptySection.apps.logs.link', { - defaultMessage: 'Install Filebeat', - }), - href: 'https://www.elastic.co', - }, - { - id: 'apm', - title: i18n.translate('xpack.observability.emptySection.apps.apm.title', { - defaultMessage: 'APM', - }), - icon: 'logoAPM', - description: i18n.translate('xpack.observability.emptySection.apps.apm.description', { - defaultMessage: - 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', - }), - linkTitle: i18n.translate('xpack.observability.emptySection.apps.apm.link', { - defaultMessage: 'Install agent', - }), - href: 'https://www.elastic.co', - }, - { - id: 'infra_metrics', - title: i18n.translate('xpack.observability.emptySection.apps.metrics.title', { - defaultMessage: 'Metrics', - }), - icon: 'logoMetrics', - description: i18n.translate('xpack.observability.emptySection.apps.metrics.description', { - defaultMessage: - 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', - }), - linkTitle: i18n.translate('xpack.observability.emptySection.apps.metrics.link', { - defaultMessage: 'Install metrics module', - }), - href: 'https://www.elastic.co', - }, - { - id: 'uptime', - title: i18n.translate('xpack.observability.emptySection.apps.uptime.title', { - defaultMessage: 'Uptime', - }), - icon: 'logoUptime', - description: i18n.translate('xpack.observability.emptySection.apps.uptime.description', { - defaultMessage: - 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', - }), - linkTitle: i18n.translate('xpack.observability.emptySection.apps.uptime.link', { - defaultMessage: 'Install Heartbeat', - }), - href: 'https://www.elastic.co', - }, - { - id: 'alert', - title: i18n.translate('xpack.observability.emptySection.apps.alert.title', { - defaultMessage: 'No alerts found.', - }), - icon: 'watchesApp', - description: i18n.translate('xpack.observability.emptySection.apps.alert.description', { - defaultMessage: - '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', - }), - linkTitle: i18n.translate('xpack.observability.emptySection.apps.alert.link', { - defaultMessage: 'Create alert', - }), - href: 'https://www.elastic.co', - }, -]; diff --git a/x-pack/plugins/observability/public/pages/overview/empty_section.ts b/x-pack/plugins/observability/public/pages/overview/empty_section.ts new file mode 100644 index 0000000000000..61456bc88bd3e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/empty_section.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { AppMountContext } from 'kibana/public'; +import { ISection } from '../../typings/section'; + +export const getEmptySections = ({ core }: { core: AppMountContext['core'] }): ISection[] => { + return [ + { + id: 'infra_logs', + title: i18n.translate('xpack.observability.emptySection.apps.logs.title', { + defaultMessage: 'Logs', + }), + icon: 'logoLogging', + description: i18n.translate('xpack.observability.emptySection.apps.logs.description', { + defaultMessage: + 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.logs.link', { + defaultMessage: 'Install Filebeat', + }), + href: 'https://www.elastic.co', + }, + { + id: 'apm', + title: i18n.translate('xpack.observability.emptySection.apps.apm.title', { + defaultMessage: 'APM', + }), + icon: 'logoAPM', + description: i18n.translate('xpack.observability.emptySection.apps.apm.description', { + defaultMessage: + 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.apm.link', { + defaultMessage: 'Install agent', + }), + href: 'https://www.elastic.co', + }, + { + id: 'infra_metrics', + title: i18n.translate('xpack.observability.emptySection.apps.metrics.title', { + defaultMessage: 'Metrics', + }), + icon: 'logoMetrics', + description: i18n.translate('xpack.observability.emptySection.apps.metrics.description', { + defaultMessage: + 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.metrics.link', { + defaultMessage: 'Install metrics module', + }), + href: 'https://www.elastic.co', + }, + { + id: 'uptime', + title: i18n.translate('xpack.observability.emptySection.apps.uptime.title', { + defaultMessage: 'Uptime', + }), + icon: 'logoUptime', + description: i18n.translate('xpack.observability.emptySection.apps.uptime.description', { + defaultMessage: + 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.uptime.link', { + defaultMessage: 'Install Heartbeat', + }), + href: 'https://www.elastic.co', + }, + { + id: 'alert', + title: i18n.translate('xpack.observability.emptySection.apps.alert.title', { + defaultMessage: 'No alerts found.', + }), + icon: 'watchesApp', + description: i18n.translate('xpack.observability.emptySection.apps.alert.description', { + defaultMessage: + '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.alert.link', { + defaultMessage: 'Create alert', + }), + href: core.http.basePath.prepend( + '/app/management/insightsAndAlerting/triggersActions/alerts' + ), + }, + ]; +}; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 55ab10b046f08..9caac7f9d86f4 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -17,13 +17,14 @@ import { MetricsSection } from '../../components/app/section/metrics'; import { UptimeSection } from '../../components/app/section/uptime'; import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; import { fetchHasData } from '../../data_handler'; -import { useFetcher, FETCH_STATUS } from '../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { RouteParams } from '../../routes'; +import { getObservabilityAlerts } from '../../services/get_observability_alerts'; import { getParsedDate } from '../../utils/date'; import { getBucketSize } from '../../utils/get_bucket_size'; -import { emptySections } from './emptySection'; +import { getEmptySections } from './empty_section'; import { LoadingObservability } from './loading_observability'; interface Props { @@ -43,15 +44,9 @@ function calculatetBucketSize({ startTime, endTime }: { startTime?: string; endT export const OverviewPage = ({ routeParams }: Props) => { const { core } = usePluginContext(); - const alerts = useFetcher(() => { - return core.http.get('/api/alerts/_find', { - query: { - page: 1, - per_page: 10, - }, - }); + const { data: alerts = [], status: alertStatus } = useFetcher(() => { + return getObservabilityAlerts({ core }); }, []); - const alertData = alerts?.data?.data; const theme = useContext(ThemeContext); const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); @@ -74,9 +69,9 @@ export const OverviewPage = ({ routeParams }: Props) => { const endTime = getParsedDate(rangeTo, { roundUp: true }); const bucketSize = calculatetBucketSize({ startTime, endTime }); - const appEmptySections = emptySections.filter(({ id }) => { + const appEmptySections = getEmptySections({ core }).filter(({ id }) => { if (id === 'alert') { - return alerts.status !== FETCH_STATUS.FAILURE && !alertData?.length; + return alertStatus !== FETCH_STATUS.FAILURE && !alerts.length; } return !hasData[id]; }); @@ -183,9 +178,9 @@ export const OverviewPage = ({ routeParams }: Props) => { {/* Alert section */} - {alertData && ( + {!!alerts.length && ( - + )} diff --git a/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts b/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts new file mode 100644 index 0000000000000..dd3f476fe7d53 --- /dev/null +++ b/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AppMountContext } from 'kibana/public'; +import { getObservabilityAlerts } from './get_observability_alerts'; + +describe('getObservabilityAlerts', () => { + it('Returns empty array when api throws exception', async () => { + const core = ({ + http: { + get: async () => { + throw new Error('Boom'); + }, + }, + } as unknown) as AppMountContext['core']; + + const alerts = await getObservabilityAlerts({ core }); + expect(alerts).toEqual([]); + }); + + it('Returns empty array when api return undefined', async () => { + const core = ({ + http: { + get: async () => { + return { + data: undefined, + }; + }, + }, + } as unknown) as AppMountContext['core']; + + const alerts = await getObservabilityAlerts({ core }); + expect(alerts).toEqual([]); + }); + + it('Shows alerts from Observability', async () => { + const core = ({ + http: { + get: async () => { + return { + data: [ + { + id: 1, + consumer: 'siem', + }, + { + id: 2, + consumer: 'apm', + }, + { + id: 3, + consumer: 'uptime', + }, + { + id: 4, + consumer: 'logs', + }, + { + id: 5, + consumer: 'metrics', + }, + ], + }; + }, + }, + } as unknown) as AppMountContext['core']; + + const alerts = await getObservabilityAlerts({ core }); + expect(alerts).toEqual([ + { + id: 2, + consumer: 'apm', + }, + { + id: 3, + consumer: 'uptime', + }, + { + id: 4, + consumer: 'logs', + }, + { + id: 5, + consumer: 'metrics', + }, + ]); + }); +}); diff --git a/x-pack/plugins/observability/public/services/get_observability_alerts.ts b/x-pack/plugins/observability/public/services/get_observability_alerts.ts new file mode 100644 index 0000000000000..1bbabbad2834a --- /dev/null +++ b/x-pack/plugins/observability/public/services/get_observability_alerts.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AppMountContext } from 'kibana/public'; +import { Alert } from '../../../alerts/common'; + +export async function getObservabilityAlerts({ core }: { core: AppMountContext['core'] }) { + try { + const { data = [] }: { data: Alert[] } = await core.http.get('/api/alerts/_find', { + query: { + page: 1, + per_page: 20, + }, + }); + + return data.filter(({ consumer }) => { + return ( + consumer === 'apm' || consumer === 'uptime' || consumer === 'logs' || consumer === 'metrics' + ); + }); + } catch (e) { + return []; + } +} From 6dbd5ec8bc859f1a6cab8653d2dfad4135afad64 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Tue, 7 Jul 2020 22:11:19 +0200 Subject: [PATCH 92/97] fixing unit test --- .../services/rest/observability.dashboard.test.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts index 09318d4f6d796..fd407a8bf72ad 100644 --- a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts +++ b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts @@ -48,25 +48,20 @@ describe('Observability dashboard data', () => { stats: { services: { type: 'number', - label: 'Services', value: 10, }, transactions: { type: 'number', - label: 'Transactions', value: 2, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', coordinates: [ { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }, ], - color: '#6092c0', }, }, }); @@ -89,21 +84,16 @@ describe('Observability dashboard data', () => { stats: { services: { type: 'number', - label: 'Services', value: 0, }, transactions: { type: 'number', - label: 'Transactions', value: 0, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', coordinates: [], - color: '#6092c0', }, }, }); @@ -126,21 +116,16 @@ describe('Observability dashboard data', () => { stats: { services: { type: 'number', - label: 'Services', value: 0, }, transactions: { type: 'number', - label: 'Transactions', value: 0, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', coordinates: [{ x: 1 }, { x: 2 }, { x: 3 }], - color: '#6092c0', }, }, }); From 6439c32060196f364b10e940ac76963373076b50 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 8 Jul 2020 09:46:10 +0200 Subject: [PATCH 93/97] fixing unit test --- .../__snapshots__/metrics_overview_fetchers.test.ts.snap | 7 ------- 1 file changed, 7 deletions(-) diff --git a/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap b/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap index 99ab129fc36e3..4680414493a2c 100644 --- a/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap +++ b/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap @@ -91,7 +91,6 @@ Object { "y": 3.5, }, ], - "label": "Inbound traffic", }, "outboundTraffic": Object { "coordinates": Array [ @@ -180,32 +179,26 @@ Object { "y": 4, }, ], - "label": "Outbound traffic", }, }, "stats": Object { "cpu": Object { - "label": "CPU usage", "type": "percent", "value": 0.0015, }, "hosts": Object { - "label": "Hosts", "type": "number", "value": 2, }, "inboundTraffic": Object { - "label": "Inbound traffic", "type": "bytesPerSecond", "value": 3.5, }, "memory": Object { - "label": "Memory usage", "type": "percent", "value": 0.0015, }, "outboundTraffic": Object { - "label": "Outbound traffic", "type": "bytesPerSecond", "value": 3, }, From 68da249294086d0cf55c8bfe3b6f92143dbd7d7c Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 8 Jul 2020 12:07:29 +0200 Subject: [PATCH 94/97] aligin beta badge to the center --- .../plugins/observability/public/components/app/header/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx index b67a62b4cba90..1c6ce766d0901 100644 --- a/x-pack/plugins/observability/public/components/app/header/index.tsx +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -60,6 +60,7 @@ export const Header = ({ defaultMessage: 'Observability', })}{' '} From ce8ec351736b1645d1ec6999d580e4cf55b3a39b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 8 Jul 2020 14:33:37 +0200 Subject: [PATCH 95/97] adding moment duration to get the units as seconds --- .../utils/get_bucket_size/unit_to_seconds.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts index 33f7a128cb5c8..657726d988495 100644 --- a/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts @@ -3,16 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import moment, { unitOfTime as UnitOfTIme } from 'moment'; + +function getDurationAsSeconds(value: number, unitOfTime: UnitOfTIme.Base) { + return moment.duration(value, unitOfTime).asSeconds(); +} const units = { - ms: 0.001, - s: 1, - m: 60, - h: 3600, - d: 86400, - w: 86400 * 7, // Hum... might be wrong - M: 86400 * 30, // this too... 29,30,31? - y: 86400 * 356, // Leap year? + ms: getDurationAsSeconds(1, 'millisecond'), + s: getDurationAsSeconds(1, 'second'), + m: getDurationAsSeconds(1, 'minute'), + h: getDurationAsSeconds(1, 'hour'), + d: getDurationAsSeconds(1, 'day'), + w: getDurationAsSeconds(1, 'week'), + M: getDurationAsSeconds(1, 'month'), + y: getDurationAsSeconds(1, 'year'), }; export function unitToSeconds(unit: string) { From d85eb3e8fcf27df30f110b9bc33885d9625b3e6b Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Wed, 8 Jul 2020 19:39:25 +0200 Subject: [PATCH 96/97] addressing PR comments --- .../plugins/observability/public/components/app/news/index.scss | 2 +- .../plugins/observability/public/components/app/news/index.tsx | 2 +- .../public/components/app/section/alerts/index.tsx | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/news/index.scss b/x-pack/plugins/observability/public/components/app/news/index.scss index d1a7eba94be67..13bcadd5f5310 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.scss +++ b/x-pack/plugins/observability/public/components/app/news/index.scss @@ -1,3 +1,3 @@ -.newItem__image{ +.observability-newsfeed-item-img{ @include euiBottomShadowSmall; } \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx index 084f1c248935e..7f16dfe066a3e 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -86,7 +86,7 @@ const NewsItem = ({ item }: { item: NewsItem }) => { height={48} alt={item.title} src={item.image_url} - className="newItem__image" + className="observability-newsfeed-item-img" /> diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 364b3ff37a631..4c80195d33ace 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -56,6 +56,7 @@ export const AlertsSection = ({ alerts }: Props) => { Date: Wed, 8 Jul 2020 19:44:29 +0200 Subject: [PATCH 97/97] addressing PR comments --- .../plugins/observability/public/components/app/news/index.scss | 2 +- .../plugins/observability/public/components/app/news/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/news/index.scss b/x-pack/plugins/observability/public/components/app/news/index.scss index 13bcadd5f5310..1222fe489c732 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.scss +++ b/x-pack/plugins/observability/public/components/app/news/index.scss @@ -1,3 +1,3 @@ -.observability-newsfeed-item-img{ +.obsNewsFeed__itemImg{ @include euiBottomShadowSmall; } \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx index 7f16dfe066a3e..41a4074f47976 100644 --- a/x-pack/plugins/observability/public/components/app/news/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -86,7 +86,7 @@ const NewsItem = ({ item }: { item: NewsItem }) => { height={48} alt={item.title} src={item.image_url} - className="observability-newsfeed-item-img" + className="obsNewsFeed__itemImg" />