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
deleted file mode 100644
index cae6b4aec0c62..0000000000000
--- a/x-pack/plugins/observability/public/components/app/news/index.test.tsx
+++ /dev/null
@@ -1,16 +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 from 'react';
-import { render } from '../../../utils/test_helper';
-import { News } from './';
-
-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/mock/news.mock.data.ts b/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts
deleted file mode 100644
index 5c623bb9134eb..0000000000000
--- a/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts
+++ /dev/null
@@ -1,34 +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 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/news/index.scss b/x-pack/plugins/observability/public/components/app/news_feed/index.scss
similarity index 100%
rename from x-pack/plugins/observability/public/components/app/news/index.scss
rename to x-pack/plugins/observability/public/components/app/news_feed/index.scss
diff --git a/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx b/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx
new file mode 100644
index 0000000000000..c71130b57c33f
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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 { NewsItem } from '../../../services/get_news_feed';
+import { render } from '../../../utils/test_helper';
+import { NewsFeed } from './';
+
+const newsFeedItems = [
+ {
+ title: {
+ en: 'Elastic introduces OpenTelemetry integration',
+ },
+ description: {
+ en:
+ 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.',
+ },
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed',
+ },
+ image_url: {
+ en: 'foo.png',
+ },
+ },
+ {
+ title: {
+ en: 'Kubernetes observability tutorial: Log monitoring and analysis',
+ },
+ description: {
+ en:
+ 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.',
+ },
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed',
+ },
+ image_url: null,
+ },
+ {
+ title: {
+ en: 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment',
+ },
+ description: {
+ en:
+ 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.',
+ },
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed',
+ },
+ image_url: {
+ en: null,
+ },
+ },
+] as NewsItem[];
+describe('News', () => {
+ it('renders resources with all elements', () => {
+ const { getByText, getAllByText, queryAllByTestId } = render(
+
+ );
+ expect(getByText("What's new")).toBeInTheDocument();
+ expect(getAllByText('Read full story').length).toEqual(3);
+ expect(queryAllByTestId('news_image').length).toEqual(1);
+ });
+});
diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news_feed/index.tsx
similarity index 53%
rename from x-pack/plugins/observability/public/components/app/news/index.tsx
rename to x-pack/plugins/observability/public/components/app/news_feed/index.tsx
index 41a4074f47976..2fbd6659bcb5a 100644
--- a/x-pack/plugins/observability/public/components/app/news/index.tsx
+++ b/x-pack/plugins/observability/public/components/app/news_feed/index.tsx
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
+ EuiErrorBoundary,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
@@ -12,51 +13,51 @@ import {
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { truncate } from 'lodash';
import React, { useContext } from 'react';
import { ThemeContext } from 'styled-components';
+import { NewsItem as INewsItem } from '../../../services/get_news_feed';
import './index.scss';
-import { truncate } from 'lodash';
-import { news as newsMockData } from './mock/news.mock.data';
-interface NewsItem {
- title: string;
- description: string;
- link_url: string;
- image_url: string;
+interface Props {
+ items: INewsItem[];
}
-export const News = () => {
- const newsItems: NewsItem[] = newsMockData;
+export const NewsFeed = ({ items }: Props) => {
return (
-
-
-
-
- {i18n.translate('xpack.observability.news.title', {
- defaultMessage: "What's new",
- })}
-
-
-
- {newsItems.map((item, index) => (
-
-
+ // The news feed is manually added/edited, to prevent any errors caused by typos or missing fields,
+ // wraps the component with EuiErrorBoundary to avoid breaking the entire page.
+
+
+
+
+
+ {i18n.translate('xpack.observability.news.title', {
+ defaultMessage: "What's new",
+ })}
+
+
- ))}
-
+ {items.map((item, index) => (
+
+
+
+ ))}
+
+
);
};
const limitString = (string: string, limit: number) => truncate(string, { length: limit });
-const NewsItem = ({ item }: { item: NewsItem }) => {
+const NewsItem = ({ item }: { item: INewsItem }) => {
const theme = useContext(ThemeContext);
return (
- {item.title}
+ {item.title.en}
@@ -65,11 +66,11 @@ const NewsItem = ({ item }: { item: NewsItem }) => {
- {limitString(item.description, 128)}
+ {limitString(item.description.en, 128)}
-
+
{i18n.translate('xpack.observability.news.readFullStory', {
defaultMessage: 'Read full story',
@@ -79,16 +80,19 @@ const NewsItem = ({ item }: { item: NewsItem }) => {
-
-
-
+ {item.image_url?.en && (
+
+
+
+ )}
diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx
index 9caac7f9d86f4..3674e69ab5702 100644
--- a/x-pack/plugins/observability/public/pages/overview/index.tsx
+++ b/x-pack/plugins/observability/public/pages/overview/index.tsx
@@ -16,6 +16,7 @@ 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 { NewsFeed } from '../../components/app/news_feed';
import { fetchHasData } from '../../data_handler';
import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher';
import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings';
@@ -26,6 +27,7 @@ import { getParsedDate } from '../../utils/date';
import { getBucketSize } from '../../utils/get_bucket_size';
import { getEmptySections } from './empty_section';
import { LoadingObservability } from './loading_observability';
+import { getNewsFeed } from '../../services/get_news_feed';
interface Props {
routeParams: RouteParams<'/overview'>;
@@ -48,6 +50,8 @@ export const OverviewPage = ({ routeParams }: Props) => {
return getObservabilityAlerts({ core });
}, []);
+ const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), []);
+
const theme = useContext(ThemeContext);
const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS);
@@ -190,6 +194,12 @@ export const OverviewPage = ({ routeParams }: Props) => {
+
+ {!!newsFeed?.items?.length && (
+
+
+
+ )}
diff --git a/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts
new file mode 100644
index 0000000000000..b23d095e2775b
--- /dev/null
+++ b/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const newsFeedFetchData = async () => {
+ return {
+ items: [
+ {
+ title: {
+ en: 'Elastic introduces OpenTelemetry integration',
+ },
+ description: {
+ en:
+ 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.',
+ },
+ link_text: null,
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed',
+ },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2020-07-02T00:00:00',
+ expire_on: '2021-05-02T00:00:00',
+ hash: '012caf3e161127d618ae8cc95e3e63f009a45d343eedf2f5e369cc95b1f9d9d3',
+ },
+ {
+ title: {
+ en: 'Kubernetes observability tutorial: Log monitoring and analysis',
+ },
+ description: {
+ en:
+ 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.',
+ },
+ link_text: null,
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed',
+ },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2020-06-23T00:00:00',
+ expire_on: '2021-06-23T00:00:00',
+ hash: '79a28cb9be717e82df80bf32c27e5d475e56d0d315be694b661d133f9a58b3b3',
+ },
+ {
+ title: {
+ en: 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment',
+ },
+ description: {
+ en:
+ 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.',
+ },
+ link_text: null,
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed',
+ },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2020-06-23T00:00:00',
+ expire_on: '2021-06-23T00:00:00',
+ hash: 'ad682c355af3d4470a14df116df3b441e941661b291cdac62335615e7c6f13c2',
+ },
+ ],
+ };
+};
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 b88614b22e81a..896cad7b72ecd 100644
--- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
+++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
@@ -17,6 +17,7 @@ import { fetchUptimeData, emptyResponse as emptyUptimeResponse } from './mock/up
import { EuiThemeProvider } from '../../typings';
import { OverviewPage } from './';
import { alertsFetchData } from './mock/alerts.mock';
+import { newsFeedFetchData } from './mock/news_feed.mock';
const core = {
http: {
@@ -102,6 +103,14 @@ const coreWithAlerts = ({
},
} as unknown) as AppMountContext['core'];
+const coreWithNewsFeed = ({
+ ...core,
+ http: {
+ ...core.http,
+ get: newsFeedFetchData,
+ },
+} as unknown) as AppMountContext['core'];
+
function unregisterAll() {
unregisterDataHandler({ appName: 'apm' });
unregisterDataHandler({ appName: 'infra_logs' });
@@ -337,6 +346,45 @@ storiesOf('app/Overview', module)
);
});
+storiesOf('app/Overview', module)
+ .addDecorator((storyFn) => (
+
+
+ {storyFn()})
+
+
+ ))
+ .add('logs, metrics, APM, Uptime and News feed', () => {
+ unregisterAll();
+ registerDataHandler({
+ appName: 'apm',
+ fetchData: fetchApmData,
+ hasData: async () => true,
+ });
+ registerDataHandler({
+ appName: 'infra_logs',
+ fetchData: fetchLogsData,
+ hasData: async () => true,
+ });
+ registerDataHandler({
+ appName: 'infra_metrics',
+ fetchData: fetchMetricsData,
+ hasData: async () => true,
+ });
+ registerDataHandler({
+ appName: 'uptime',
+ fetchData: fetchUptimeData,
+ hasData: async () => true,
+ });
+ return (
+
+ );
+ });
+
storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
diff --git a/x-pack/plugins/observability/public/services/get_news_feed.test.ts b/x-pack/plugins/observability/public/services/get_news_feed.test.ts
new file mode 100644
index 0000000000000..49eb2da803ab6
--- /dev/null
+++ b/x-pack/plugins/observability/public/services/get_news_feed.test.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { getNewsFeed } from './get_news_feed';
+import { AppMountContext } from 'kibana/public';
+
+describe('getNewsFeed', () => {
+ it('Returns empty array when api throws exception', async () => {
+ const core = ({
+ http: {
+ get: async () => {
+ throw new Error('Boom');
+ },
+ },
+ } as unknown) as AppMountContext['core'];
+
+ const newsFeed = await getNewsFeed({ core });
+ expect(newsFeed.items).toEqual([]);
+ });
+ it('Returns array with the news feed', async () => {
+ const core = ({
+ http: {
+ get: async () => {
+ return {
+ items: [
+ {
+ title: {
+ en: 'Elastic introduces OpenTelemetry integration',
+ },
+ description: {
+ en:
+ 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.',
+ },
+ link_text: null,
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed',
+ },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2020-07-02T00:00:00',
+ expire_on: '2021-05-02T00:00:00',
+ hash: '012caf3e161127d618ae8cc95e3e63f009a45d343eedf2f5e369cc95b1f9d9d3',
+ },
+ {
+ title: {
+ en: 'Kubernetes observability tutorial: Log monitoring and analysis',
+ },
+ description: {
+ en:
+ 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.',
+ },
+ link_text: null,
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed',
+ },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2020-06-23T00:00:00',
+ expire_on: '2021-06-23T00:00:00',
+ hash: '79a28cb9be717e82df80bf32c27e5d475e56d0d315be694b661d133f9a58b3b3',
+ },
+ {
+ title: {
+ en:
+ 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment',
+ },
+ description: {
+ en:
+ 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.',
+ },
+ link_text: null,
+ link_url: {
+ en:
+ 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed',
+ },
+ languages: null,
+ badge: null,
+ image_url: null,
+ publish_on: '2020-06-23T00:00:00',
+ expire_on: '2021-06-23T00:00:00',
+ hash: 'ad682c355af3d4470a14df116df3b441e941661b291cdac62335615e7c6f13c2',
+ },
+ ],
+ };
+ },
+ },
+ } as unknown) as AppMountContext['core'];
+
+ const newsFeed = await getNewsFeed({ core });
+ expect(newsFeed.items.length).toEqual(3);
+ });
+});
diff --git a/x-pack/plugins/observability/public/services/get_news_feed.ts b/x-pack/plugins/observability/public/services/get_news_feed.ts
new file mode 100644
index 0000000000000..485bd2da20754
--- /dev/null
+++ b/x-pack/plugins/observability/public/services/get_news_feed.ts
@@ -0,0 +1,25 @@
+/*
+ * 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';
+
+export interface NewsItem {
+ title: { en: string };
+ description: { en: string };
+ link_url: { en: string };
+ image_url?: { en: string } | null;
+}
+
+interface NewsFeed {
+ items: NewsItem[] | [];
+}
+
+export async function getNewsFeed({ core }: { core: AppMountContext['core'] }): Promise {
+ try {
+ return await core.http.get('https://feeds.elastic.co/observability-solution/v8.0.0.json');
+ } catch (e) {
+ return { items: [] };
+ }
+}