This commit is contained in:
parent
c63d600350
commit
fa4cd2c152
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { LegacyPluginApi, LegacyPluginSpec, ArrayOrItem } from 'src/legacy/plugin_discovery/types';
|
||||
import { Legacy } from 'kibana';
|
||||
import { NewsfeedPluginInjectedConfig } from '../../../plugins/newsfeed/types';
|
||||
import {
|
||||
PLUGIN_ID,
|
||||
DEFAULT_SERVICE_URLROOT,
|
||||
DEV_SERVICE_URLROOT,
|
||||
DEFAULT_SERVICE_PATH,
|
||||
} from './constants';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function(kibana: LegacyPluginApi): ArrayOrItem<LegacyPluginSpec> {
|
||||
const pluginSpec: Legacy.PluginSpecOptions = {
|
||||
id: PLUGIN_ID,
|
||||
config(Joi: any) {
|
||||
// NewsfeedPluginInjectedConfig in Joi form
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
service: Joi.object({
|
||||
pathTemplate: Joi.string().default(DEFAULT_SERVICE_PATH),
|
||||
urlRoot: Joi.when('$prod', {
|
||||
is: true,
|
||||
then: Joi.string().default(DEFAULT_SERVICE_URLROOT),
|
||||
otherwise: Joi.string().default(DEV_SERVICE_URLROOT),
|
||||
}),
|
||||
}).default(),
|
||||
defaultLanguage: Joi.string().default('en'),
|
||||
mainInterval: Joi.number().default(120 * 1000), // (2min) How often to retry failed fetches, and/or check if newsfeed items need to be refreshed from remote
|
||||
fetchInterval: Joi.number().default(86400 * 1000), // (1day) How often to fetch remote and reset the last fetched time
|
||||
}).default();
|
||||
},
|
||||
uiExports: {
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
injectDefaultVars(server): NewsfeedPluginInjectedConfig {
|
||||
const config = server.config();
|
||||
return {
|
||||
newsfeed: {
|
||||
service: {
|
||||
pathTemplate: config.get('newsfeed.service.pathTemplate') as string,
|
||||
urlRoot: config.get('newsfeed.service.urlRoot') as string,
|
||||
},
|
||||
defaultLanguage: config.get('newsfeed.defaultLanguage') as string,
|
||||
mainInterval: config.get('newsfeed.mainInterval') as number,
|
||||
fetchInterval: config.get('newsfeed.fetchInterval') as number,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
return new kibana.Plugin(pluginSpec);
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "newsfeed",
|
||||
"version": "kibana"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
@import 'src/legacy/ui/public/styles/styling_constants';
|
||||
|
||||
@import './np_ready/components/header_alert/_index';
|
|
@ -1,27 +0,0 @@
|
|||
@import '@elastic/eui/src/components/header/variables';
|
||||
|
||||
.kbnNews__flyout {
|
||||
top: $euiHeaderChildSize + 1px;
|
||||
height: calc(100% - #{$euiHeaderChildSize});
|
||||
}
|
||||
|
||||
.kbnNewsFeed__headerAlert.euiHeaderAlert {
|
||||
margin-bottom: $euiSizeL;
|
||||
padding: 0 $euiSizeS $euiSizeL;
|
||||
border-bottom: $euiBorderThin;
|
||||
border-top: none;
|
||||
|
||||
.euiHeaderAlert__title {
|
||||
@include euiTitle('xs');
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
||||
|
||||
.euiHeaderAlert__text {
|
||||
@include euiFontSizeS;
|
||||
margin-bottom: $euiSize;
|
||||
}
|
||||
|
||||
.euiHeaderAlert__action {
|
||||
@include euiFontSizeS;
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiI18n } from '@elastic/eui';
|
||||
|
||||
interface IEuiHeaderAlertProps {
|
||||
action: JSX.Element;
|
||||
className?: string;
|
||||
date: string;
|
||||
text: string;
|
||||
title: string;
|
||||
badge?: JSX.Element;
|
||||
rest?: string[];
|
||||
}
|
||||
|
||||
export const EuiHeaderAlert = ({
|
||||
action,
|
||||
className,
|
||||
date,
|
||||
text,
|
||||
title,
|
||||
badge,
|
||||
...rest
|
||||
}: IEuiHeaderAlertProps) => {
|
||||
const classes = classNames('euiHeaderAlert', 'kbnNewsFeed__headerAlert', className);
|
||||
|
||||
const badgeContent = badge || null;
|
||||
|
||||
return (
|
||||
<EuiI18n token="euiHeaderAlert.dismiss" default="Dismiss">
|
||||
{(dismiss: any) => (
|
||||
<div className={classes} {...rest}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<div className="euiHeaderAlert__date">{date}</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{badgeContent}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<div className="euiHeaderAlert__title">{title}</div>
|
||||
<div className="euiHeaderAlert__text">{text}</div>
|
||||
<div className="euiHeaderAlert__action euiLink">{action}</div>
|
||||
</div>
|
||||
)}
|
||||
</EuiI18n>
|
||||
);
|
||||
};
|
||||
|
||||
EuiHeaderAlert.propTypes = {
|
||||
action: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
date: PropTypes.node.isRequired,
|
||||
text: PropTypes.node,
|
||||
title: PropTypes.node.isRequired,
|
||||
badge: PropTypes.node,
|
||||
};
|
|
@ -17,17 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Hapi from 'hapi';
|
||||
import { initPlugin as initNewsfeed } from './newsfeed_simulation';
|
||||
export const NEWSFEED_FALLBACK_LANGUAGE = 'en';
|
||||
export const NEWSFEED_LAST_FETCH_STORAGE_KEY = 'newsfeed.lastfetchtime';
|
||||
export const NEWSFEED_HASH_SET_STORAGE_KEY = 'newsfeed.hashes';
|
||||
|
||||
const NAME = 'newsfeed-FTS-external-service-simulators';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function(kibana: any) {
|
||||
return new kibana.Plugin({
|
||||
name: NAME,
|
||||
init: (server: Hapi.Server) => {
|
||||
initNewsfeed(server, `/api/_${NAME}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
export const NEWSFEED_DEFAULT_SERVICE_BASE_URL = 'https://feeds.elastic.co';
|
||||
export const NEWSFEED_DEV_SERVICE_BASE_URL = 'https://feeds-staging.elastic.co';
|
||||
export const NEWSFEED_DEFAULT_SERVICE_PATH = '/kibana/v{VERSION}.json';
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "newsfeed",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"server": true,
|
||||
"ui": true
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@ import {
|
|||
EuiButtonEmpty,
|
||||
EuiText,
|
||||
EuiBadge,
|
||||
EuiHeaderAlert,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiHeaderAlert } from '../../../../legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/header_alert';
|
||||
import { NewsfeedContext } from './newsfeed_header_nav_button';
|
||||
import { NewsfeedItem } from '../../types';
|
||||
import { NewsfeedItem } from '../types';
|
||||
import { NewsEmptyPrompt } from './empty_news';
|
||||
import { NewsLoadingPrompt } from './loading_news';
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import React, { useState, Fragment, useEffect } from 'react';
|
|||
import * as Rx from 'rxjs';
|
||||
import { EuiHeaderSectionItemButton, EuiIcon, EuiNotificationBadge } from '@elastic/eui';
|
||||
import { NewsfeedFlyout } from './flyout_list';
|
||||
import { FetchResult } from '../../types';
|
||||
import { FetchResult } from '../types';
|
||||
|
||||
export interface INewsfeedContext {
|
||||
setFlyoutVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
|
|
@ -22,8 +22,11 @@ import { interval, race } from 'rxjs';
|
|||
import sinon, { stub } from 'sinon';
|
||||
import moment from 'moment';
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
import { NEWSFEED_HASH_SET_STORAGE_KEY, NEWSFEED_LAST_FETCH_STORAGE_KEY } from '../../constants';
|
||||
import { ApiItem, NewsfeedItem, NewsfeedPluginInjectedConfig } from '../../types';
|
||||
import {
|
||||
NEWSFEED_HASH_SET_STORAGE_KEY,
|
||||
NEWSFEED_LAST_FETCH_STORAGE_KEY,
|
||||
} from '../../common/constants';
|
||||
import { ApiItem, NewsfeedItem, NewsfeedPluginBrowserConfig } from '../types';
|
||||
import { NewsfeedApiDriver, getApi } from './api';
|
||||
|
||||
const localStorageGet = sinon.stub();
|
||||
|
@ -458,7 +461,7 @@ describe('getApi', () => {
|
|||
}
|
||||
return Promise.reject('wrong args!');
|
||||
};
|
||||
let configMock: NewsfeedPluginInjectedConfig;
|
||||
let configMock: NewsfeedPluginBrowserConfig;
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
@ -466,15 +469,12 @@ describe('getApi', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
configMock = {
|
||||
newsfeed: {
|
||||
service: {
|
||||
urlRoot: 'http://fakenews.co',
|
||||
pathTemplate: '/kibana-test/v{VERSION}.json',
|
||||
},
|
||||
defaultLanguage: 'en',
|
||||
mainInterval: 86400000,
|
||||
fetchInterval: 86400000,
|
||||
service: {
|
||||
urlRoot: 'http://fakenews.co',
|
||||
pathTemplate: '/kibana-test/v{VERSION}.json',
|
||||
},
|
||||
mainInterval: moment.duration(86400000),
|
||||
fetchInterval: moment.duration(86400000),
|
||||
};
|
||||
httpMock = ({
|
||||
fetch: mockHttpGet,
|
||||
|
@ -483,7 +483,7 @@ describe('getApi', () => {
|
|||
|
||||
it('creates a result', done => {
|
||||
mockHttpGet.mockImplementationOnce(() => Promise.resolve({ items: [] }));
|
||||
getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
|
||||
getApi(httpMock, configMock, '6.8.2').subscribe(result => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"error": null,
|
||||
|
@ -528,7 +528,7 @@ describe('getApi', () => {
|
|||
|
||||
mockHttpGet.mockImplementationOnce(getHttpMockWithItems(mockApiItems));
|
||||
|
||||
getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
|
||||
getApi(httpMock, configMock, '6.8.2').subscribe(result => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"error": null,
|
||||
|
@ -568,7 +568,7 @@ describe('getApi', () => {
|
|||
},
|
||||
];
|
||||
mockHttpGet.mockImplementationOnce(getHttpMockWithItems(mockApiItems));
|
||||
getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
|
||||
getApi(httpMock, configMock, '6.8.2').subscribe(result => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"error": null,
|
||||
|
@ -595,7 +595,7 @@ describe('getApi', () => {
|
|||
it('forwards an error', done => {
|
||||
mockHttpGet.mockImplementationOnce((arg1, arg2) => Promise.reject('sorry, try again later!'));
|
||||
|
||||
getApi(httpMock, configMock.newsfeed, '6.8.2').subscribe(result => {
|
||||
getApi(httpMock, configMock, '6.8.2').subscribe(result => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"error": "sorry, try again later!",
|
||||
|
@ -623,14 +623,14 @@ describe('getApi', () => {
|
|||
];
|
||||
|
||||
it("retries until fetch doesn't error", done => {
|
||||
configMock.newsfeed.mainInterval = 10; // fast retry for testing
|
||||
configMock.mainInterval = moment.duration(10); // fast retry for testing
|
||||
mockHttpGet
|
||||
.mockImplementationOnce(() => Promise.reject('Sorry, try again later!'))
|
||||
.mockImplementationOnce(() => Promise.reject('Sorry, internal server error!'))
|
||||
.mockImplementationOnce(() => Promise.reject("Sorry, it's too cold to go outside!"))
|
||||
.mockImplementationOnce(getHttpMockWithItems(successItems));
|
||||
|
||||
getApi(httpMock, configMock.newsfeed, '6.8.2')
|
||||
getApi(httpMock, configMock, '6.8.2')
|
||||
.pipe(take(4), toArray())
|
||||
.subscribe(result => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -677,13 +677,13 @@ describe('getApi', () => {
|
|||
});
|
||||
|
||||
it("doesn't retry if fetch succeeds", done => {
|
||||
configMock.newsfeed.mainInterval = 10; // fast retry for testing
|
||||
configMock.mainInterval = moment.duration(10); // fast retry for testing
|
||||
mockHttpGet.mockImplementation(getHttpMockWithItems(successItems));
|
||||
|
||||
const timeout$ = interval(1000); // lets us capture some results after a short time
|
||||
let timesFetched = 0;
|
||||
|
||||
const get$ = getApi(httpMock, configMock.newsfeed, '6.8.2').pipe(
|
||||
const get$ = getApi(httpMock, configMock, '6.8.2').pipe(
|
||||
tap(() => {
|
||||
timesFetched++;
|
||||
})
|
||||
|
|
|
@ -26,10 +26,10 @@ import {
|
|||
NEWSFEED_FALLBACK_LANGUAGE,
|
||||
NEWSFEED_LAST_FETCH_STORAGE_KEY,
|
||||
NEWSFEED_HASH_SET_STORAGE_KEY,
|
||||
} from '../../constants';
|
||||
import { NewsfeedPluginInjectedConfig, ApiItem, NewsfeedItem, FetchResult } from '../../types';
|
||||
} from '../../common/constants';
|
||||
import { ApiItem, NewsfeedItem, FetchResult, NewsfeedPluginBrowserConfig } from '../types';
|
||||
|
||||
type ApiConfig = NewsfeedPluginInjectedConfig['newsfeed']['service'];
|
||||
type ApiConfig = NewsfeedPluginBrowserConfig['service'];
|
||||
|
||||
export class NewsfeedApiDriver {
|
||||
private readonly loadedTime = moment().utc(); // the date is compared to time in UTC format coming from the service
|
||||
|
@ -167,14 +167,14 @@ export class NewsfeedApiDriver {
|
|||
*/
|
||||
export function getApi(
|
||||
http: HttpSetup,
|
||||
config: NewsfeedPluginInjectedConfig['newsfeed'],
|
||||
config: NewsfeedPluginBrowserConfig,
|
||||
kibanaVersion: string
|
||||
): Rx.Observable<void | FetchResult> {
|
||||
const userLanguage = i18n.getLocale() || config.defaultLanguage;
|
||||
const fetchInterval = config.fetchInterval;
|
||||
const userLanguage = i18n.getLocale();
|
||||
const fetchInterval = config.fetchInterval.asMilliseconds();
|
||||
const driver = new NewsfeedApiDriver(kibanaVersion, userLanguage, fetchInterval);
|
||||
|
||||
return Rx.timer(0, config.mainInterval).pipe(
|
||||
return Rx.timer(0, config.mainInterval.asMilliseconds()).pipe(
|
||||
filter(() => driver.shouldFetch()),
|
||||
mergeMap(() =>
|
||||
driver.fetchNewsfeedItems(http, config.service).pipe(
|
||||
|
|
|
@ -21,9 +21,10 @@ import * as Rx from 'rxjs';
|
|||
import { catchError, takeUntil } from 'rxjs/operators';
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
|
||||
import { FetchResult, NewsfeedPluginInjectedConfig } from '../types';
|
||||
import { NewsfeedPluginBrowserConfig } from './types';
|
||||
import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button';
|
||||
import { getApi } from './lib/api';
|
||||
|
||||
|
@ -32,10 +33,18 @@ export type Start = object;
|
|||
|
||||
export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
|
||||
private readonly kibanaVersion: string;
|
||||
private readonly config: NewsfeedPluginBrowserConfig;
|
||||
private readonly stop$ = new Rx.ReplaySubject(1);
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
constructor(initializerContext: PluginInitializerContext<NewsfeedPluginBrowserConfig>) {
|
||||
this.kibanaVersion = initializerContext.env.packageInfo.version;
|
||||
const config = initializerContext.config.get();
|
||||
this.config = Object.freeze({
|
||||
...config,
|
||||
// We need wrap them in moment.duration because exposeToBrowser stringifies it.
|
||||
mainInterval: moment.duration(config.mainInterval),
|
||||
fetchInterval: moment.duration(config.fetchInterval),
|
||||
});
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup): Setup {
|
||||
|
@ -57,16 +66,8 @@ export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
|
|||
}
|
||||
|
||||
private fetchNewsfeed(core: CoreStart) {
|
||||
const { http, injectedMetadata } = core;
|
||||
const config = injectedMetadata.getInjectedVar('newsfeed') as
|
||||
| NewsfeedPluginInjectedConfig['newsfeed']
|
||||
| undefined;
|
||||
|
||||
if (!config) {
|
||||
// running in new platform, injected metadata not available
|
||||
return new Rx.Observable<void | FetchResult | null>();
|
||||
}
|
||||
return getApi(http, config, this.kibanaVersion).pipe(
|
||||
const { http } = core;
|
||||
return getApi(http, this.config, this.kibanaVersion).pipe(
|
||||
takeUntil(this.stop$), // stop the interval when stop method is called
|
||||
catchError(() => Rx.of(null)) // do not throw error
|
||||
);
|
||||
|
|
|
@ -17,18 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Moment } from 'moment';
|
||||
import { Duration, Moment } from 'moment';
|
||||
|
||||
export interface NewsfeedPluginInjectedConfig {
|
||||
newsfeed: {
|
||||
service: {
|
||||
urlRoot: string;
|
||||
pathTemplate: string;
|
||||
};
|
||||
defaultLanguage: string;
|
||||
mainInterval: number; // how often to check last updated time
|
||||
fetchInterval: number; // how often to fetch remote service and set last updated
|
||||
// Ideally, we may want to obtain the type from the configSchema and exposeToBrowser keys...
|
||||
export interface NewsfeedPluginBrowserConfig {
|
||||
service: {
|
||||
urlRoot: string;
|
||||
pathTemplate: string;
|
||||
};
|
||||
mainInterval: Duration; // how often to check last updated time
|
||||
fetchInterval: Duration; // how often to fetch remote service and set last updated
|
||||
}
|
||||
|
||||
export interface ApiItem {
|
44
src/plugins/newsfeed/server/config.ts
Normal file
44
src/plugins/newsfeed/server/config.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import {
|
||||
NEWSFEED_DEFAULT_SERVICE_PATH,
|
||||
NEWSFEED_DEFAULT_SERVICE_BASE_URL,
|
||||
NEWSFEED_DEV_SERVICE_BASE_URL,
|
||||
NEWSFEED_FALLBACK_LANGUAGE,
|
||||
} from '../common/constants';
|
||||
|
||||
export const configSchema = schema.object({
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
service: schema.object({
|
||||
pathTemplate: schema.string({ defaultValue: NEWSFEED_DEFAULT_SERVICE_PATH }),
|
||||
urlRoot: schema.conditional(
|
||||
schema.contextRef('prod'),
|
||||
schema.literal(true), // Point to staging if it's not a production release
|
||||
schema.string({ defaultValue: NEWSFEED_DEFAULT_SERVICE_BASE_URL }),
|
||||
schema.string({ defaultValue: NEWSFEED_DEV_SERVICE_BASE_URL })
|
||||
),
|
||||
}),
|
||||
defaultLanguage: schema.string({ defaultValue: NEWSFEED_FALLBACK_LANGUAGE }), // TODO: Deprecate since no longer used
|
||||
mainInterval: schema.duration({ defaultValue: '2m' }), // (2min) How often to retry failed fetches, and/or check if newsfeed items need to be refreshed from remote
|
||||
fetchInterval: schema.duration({ defaultValue: '1d' }), // (1day) How often to fetch remote and reset the last fetched time
|
||||
});
|
||||
|
||||
export type NewsfeedConfigType = TypeOf<typeof configSchema>;
|
36
src/plugins/newsfeed/server/index.ts
Normal file
36
src/plugins/newsfeed/server/index.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { PluginConfigDescriptor } from 'kibana/server';
|
||||
import { NewsfeedPlugin } from './plugin';
|
||||
import { configSchema, NewsfeedConfigType } from './config';
|
||||
|
||||
export const config: PluginConfigDescriptor<NewsfeedConfigType> = {
|
||||
schema: configSchema,
|
||||
exposeToBrowser: {
|
||||
service: true,
|
||||
mainInterval: true,
|
||||
fetchInterval: true,
|
||||
},
|
||||
deprecations: ({ unused }) => [unused('defaultLanguage')],
|
||||
};
|
||||
|
||||
export function plugin() {
|
||||
return new NewsfeedPlugin();
|
||||
}
|
|
@ -17,6 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export const NEWSFEED_FALLBACK_LANGUAGE = 'en';
|
||||
export const NEWSFEED_LAST_FETCH_STORAGE_KEY = 'newsfeed.lastfetchtime';
|
||||
export const NEWSFEED_HASH_SET_STORAGE_KEY = 'newsfeed.hashes';
|
||||
import { Plugin } from 'kibana/server';
|
||||
|
||||
export class NewsfeedPlugin implements Plugin {
|
||||
public setup() {}
|
||||
|
||||
public start() {}
|
||||
|
||||
public stop() {}
|
||||
}
|
6
test/common/fixtures/plugins/newsfeed/kibana.json
Normal file
6
test/common/fixtures/plugins/newsfeed/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "newsfeed-fixtures",
|
||||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Hapi from 'hapi';
|
||||
|
||||
interface WebhookRequest extends Hapi.Request {
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export async function initPlugin(server: Hapi.Server, path: string) {
|
||||
server.route({
|
||||
method: ['GET'],
|
||||
path: `${path}/kibana/v{version}.json`,
|
||||
options: {
|
||||
cors: {
|
||||
origin: ['*'],
|
||||
additionalHeaders: [
|
||||
'Sec-Fetch-Mode',
|
||||
'Access-Control-Request-Method',
|
||||
'Access-Control-Request-Headers',
|
||||
'cache-control',
|
||||
'x-requested-with',
|
||||
'Origin',
|
||||
'User-Agent',
|
||||
'DNT',
|
||||
'content-type',
|
||||
'kbn-version',
|
||||
],
|
||||
},
|
||||
},
|
||||
handler: newsfeedHandler as Hapi.Lifecycle.Method,
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: ['GET'],
|
||||
path: `${path}/kibana/crash.json`,
|
||||
options: {
|
||||
cors: {
|
||||
origin: ['*'],
|
||||
additionalHeaders: [
|
||||
'Sec-Fetch-Mode',
|
||||
'Access-Control-Request-Method',
|
||||
'Access-Control-Request-Headers',
|
||||
'cache-control',
|
||||
'x-requested-with',
|
||||
'Origin',
|
||||
'User-Agent',
|
||||
'DNT',
|
||||
'content-type',
|
||||
'kbn-version',
|
||||
],
|
||||
},
|
||||
},
|
||||
handler() {
|
||||
throw new Error('Internal server error');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function newsfeedHandler(request: WebhookRequest, h: any) {
|
||||
return htmlResponse(h, 200, JSON.stringify(mockNewsfeed(request.params.version)));
|
||||
}
|
||||
|
||||
const mockNewsfeed = (version: string) => ({
|
||||
items: [
|
||||
{
|
||||
title: { en: `You are functionally testing the newsfeed widget with fixtures!` },
|
||||
description: { en: 'See test/common/fixtures/plugins/newsfeed/newsfeed_simulation' },
|
||||
link_text: { en: 'Generic feed-viewer could go here' },
|
||||
link_url: { en: 'https://feeds.elastic.co' },
|
||||
languages: null,
|
||||
badge: null,
|
||||
image_url: null,
|
||||
publish_on: '2019-06-21T00:00:00',
|
||||
expire_on: '2040-01-31T00:00:00',
|
||||
hash: '39ca7d409c7eb25f4c69a5a6a11309b2f5ced7ca3f9b3a0109517126e0fd91ca',
|
||||
},
|
||||
{
|
||||
title: { en: 'Staging too!' },
|
||||
description: { en: 'Hello world' },
|
||||
link_text: { en: 'Generic feed-viewer could go here' },
|
||||
link_url: { en: 'https://feeds-staging.elastic.co' },
|
||||
languages: null,
|
||||
badge: null,
|
||||
image_url: null,
|
||||
publish_on: '2019-06-21T00:00:00',
|
||||
expire_on: '2040-01-31T00:00:00',
|
||||
hash: 'db445c9443eb50ea2eb15f20edf89cf0f7dac2b058b11cafc2c8c288b6e4ce2a',
|
||||
},
|
||||
{
|
||||
title: { en: 'This item is expired!' },
|
||||
description: { en: 'This should not show up.' },
|
||||
link_text: { en: 'Generic feed-viewer could go here' },
|
||||
link_url: { en: 'https://feeds-staging.elastic.co' },
|
||||
languages: null,
|
||||
badge: null,
|
||||
image_url: null,
|
||||
publish_on: '2019-06-21T00:00:00',
|
||||
expire_on: '2019-12-31T00:00:00',
|
||||
hash: 'db445c9443eb50ea2eb15f20edf89cf0f7dac2b058b11cafc2c8c288b6e4ce2a',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function htmlResponse(h: any, code: number, text: string) {
|
||||
return h
|
||||
.response(text)
|
||||
.type('application/json')
|
||||
.code(code);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "newsfeed-fixtures",
|
||||
"version": "0.0.0",
|
||||
"kibana": {
|
||||
"version": "kibana"
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export const PLUGIN_ID = 'newsfeed';
|
||||
export const DEFAULT_SERVICE_URLROOT = 'https://feeds.elastic.co';
|
||||
export const DEV_SERVICE_URLROOT = 'https://feeds-staging.elastic.co';
|
||||
export const DEFAULT_SERVICE_PATH = '/kibana/v{VERSION}.json';
|
||||
import { PluginInitializerContext } from 'kibana/public';
|
||||
import { NewsFeedSimulatorPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new NewsFeedSimulatorPlugin(initializerContext);
|
||||
}
|
97
test/common/fixtures/plugins/newsfeed/server/plugin.ts
Normal file
97
test/common/fixtures/plugins/newsfeed/server/plugin.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, Plugin } from 'kibana/server';
|
||||
import { PluginInitializerContext } from 'kibana/public';
|
||||
|
||||
export class NewsFeedSimulatorPlugin implements Plugin {
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup({ http }: CoreSetup) {
|
||||
const router = http.createRouter();
|
||||
const version = this.initializerContext.env.packageInfo.version;
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: `/api/_newsfeed-FTS-external-service-simulators/kibana/v${version}.json`,
|
||||
validate: false,
|
||||
options: { authRequired: false },
|
||||
},
|
||||
(context, req, res) => {
|
||||
return res.ok({ body: this.mockNewsfeed() });
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/api/_newsfeed-FTS-external-service-simulators/kibana/crash.json',
|
||||
validate: false,
|
||||
options: { authRequired: false },
|
||||
},
|
||||
(context, req, res) => {
|
||||
return res.internalError({ body: new Error('Internal server error') });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public start() {}
|
||||
|
||||
private mockNewsfeed() {
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
title: { en: `You are functionally testing the newsfeed widget with fixtures!` },
|
||||
description: { en: 'See test/common/fixtures/plugins/newsfeed/newsfeed_simulation' },
|
||||
link_text: { en: 'Generic feed-viewer could go here' },
|
||||
link_url: { en: 'https://feeds.elastic.co' },
|
||||
languages: null,
|
||||
badge: null,
|
||||
image_url: null,
|
||||
publish_on: '2019-06-21T00:00:00',
|
||||
expire_on: '2040-01-31T00:00:00',
|
||||
hash: '39ca7d409c7eb25f4c69a5a6a11309b2f5ced7ca3f9b3a0109517126e0fd91ca',
|
||||
},
|
||||
{
|
||||
title: { en: 'Staging too!' },
|
||||
description: { en: 'Hello world' },
|
||||
link_text: { en: 'Generic feed-viewer could go here' },
|
||||
link_url: { en: 'https://feeds-staging.elastic.co' },
|
||||
languages: null,
|
||||
badge: null,
|
||||
image_url: null,
|
||||
publish_on: '2019-06-21T00:00:00',
|
||||
expire_on: '2040-01-31T00:00:00',
|
||||
hash: 'db445c9443eb50ea2eb15f20edf89cf0f7dac2b058b11cafc2c8c288b6e4ce2a',
|
||||
},
|
||||
{
|
||||
title: { en: 'This item is expired!' },
|
||||
description: { en: 'This should not show up.' },
|
||||
link_text: { en: 'Generic feed-viewer could go here' },
|
||||
link_url: { en: 'https://feeds-staging.elastic.co' },
|
||||
languages: null,
|
||||
badge: null,
|
||||
image_url: null,
|
||||
publish_on: '2019-06-21T00:00:00',
|
||||
expire_on: '2019-12-31T00:00:00',
|
||||
hash: 'db445c9443eb50ea2eb15f20edf89cf0f7dac2b058b11cafc2c8c288b6e4ce2a',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue