Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[skip-ci] [Newsfeed] add a feed items observable #49581

Merged
merged 1 commit into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions x-pack/plugins/newsfeed/public/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,112 @@
* 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 Rx from 'rxjs';
import moment from 'moment';
import { filter, mergeMap, tap } from 'rxjs/operators';
import { HttpServiceBase } from '../../../../../src/core/public';

interface ApiItem {
hash: string;
expire_on: Date;
title: { [lang: string]: string };
description: { [lang: string]: string };
link_text: { [lang: string]: string };
link_url: { [lang: string]: string };

badge: null; // not used phase 1
image_url: null; // not used phase 1
languages: null; // not used phase 1
publish_on: null; // not used phase 1
}

interface NewsfeedItem {
title: string;
description: string;
linkText: string;
linkUrl: string;
}

interface FetchResult {
hasNew: boolean;
feedItems: NewsfeedItem[];
}

const DEFAULT_LANGUAGE = 'en'; // TODO: read from settings, default to en
const NEWSFEED_MAIN_INTERVAL = 120000; // A main interval to check for need to refresh (2min)
const NEWSFEED_FETCH_INTERVAL = moment.duration(1, 'day'); // how often to actually fetch the API
const NEWSFEED_LAST_FETCH_STORAGE_KEY = 'xpack.newsfeed.lastfetchtime';
const NEWSFEED_HASH_SET_STORAGE_KEY = 'xpack.newsfeed.hashes';
const NEWSFEED_SERVICE_URL = 'https://feeds.elastic.co/kibana/v7.0.0.json'; // FIXME: should be dynamic

function shouldFetch(): boolean {
const lastFetch: string | null = localStorage.getItem(NEWSFEED_LAST_FETCH_STORAGE_KEY);
if (lastFetch == null) {
return true;
}
const last = moment(lastFetch, 'x'); // parse as unix ms timestamp
const now = moment();
const duration = moment.duration(now.diff(last));

return duration > NEWSFEED_FETCH_INTERVAL;
}

function updateLastFetch() {
localStorage.setItem(NEWSFEED_LAST_FETCH_STORAGE_KEY, Date.now().toString());
}

function updateHashes(items: ApiItem[]): { previous: string[]; current: string[] } {
// combine localStorage hashes with new hashes
const hashSet: string | null = localStorage.getItem(NEWSFEED_HASH_SET_STORAGE_KEY);
let oldHashes: string[] = [];
if (hashSet != null) {
oldHashes = hashSet.split(',');
}
const newHashes = items.map(i => i.hash.slice(0, 10));
const updatedHashes = [...new Set(oldHashes.concat(newHashes))];
localStorage.setItem(NEWSFEED_HASH_SET_STORAGE_KEY, updatedHashes.join(','));

return { previous: oldHashes, current: updatedHashes };
}

/*
* Creates an Observable to newsfeed items, powered by the main interval
* Computes hasNew value from new item hashes saved in localStorage
*/
export function getApi(http: HttpServiceBase): Rx.Observable<void | FetchResult> {
return Rx.timer(0, NEWSFEED_MAIN_INTERVAL).pipe(
filter(() => shouldFetch()),
mergeMap(
(value: number): Rx.Observable<ApiItem[]> => {
return Rx.from(
http.fetch(NEWSFEED_SERVICE_URL, { method: 'GET' }).then(({ items }) => items)
);
}
),
filter(items => items.length > 0),
tap(() => updateLastFetch()),
mergeMap(
(items): Rx.Observable<FetchResult> => {
// calculate hasNew
const { previous, current } = updateHashes(items);
const hasNew = current.length > previous.length;

// model feed items
const feedItems: NewsfeedItem[] = items.map(it => {
return {
title: it.title[DEFAULT_LANGUAGE],
description: it.description[DEFAULT_LANGUAGE],
linkText: it.link_text[DEFAULT_LANGUAGE],
linkUrl: it.link_url[DEFAULT_LANGUAGE],
};
});

return Rx.of({
hasNew,
feedItems,
});
}
)
);
}
22 changes: 22 additions & 0 deletions x-pack/plugins/newsfeed/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as Rx from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators';
import ReactDOM from 'react-dom';
import React from 'react';
import {
Expand All @@ -12,12 +14,15 @@ import {
CoreStart,
Plugin,
} from '../../../../src/core/public';
import { getApi } from './lib/api';
import { MailNavButton } from './components/spaces_header_nav_button';

export type Setup = void;
export type Start = void;

export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
private readonly stop$ = new Rx.ReplaySubject(1);

constructor(initializerContext: PluginInitializerContext) {}

public setup(core: CoreSetup): Setup {}
Expand All @@ -32,5 +37,22 @@ export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
order: 1000,
mount,
});

const { http } = core;
const api$ = getApi(http).pipe(
takeUntil(this.stop$), // stop the interval when stop method is called
catchError(() => {
// show a message to try again later?
// do not throw error
return Rx.of(null);
})
);

// TODO: pass to component?
api$.subscribe();
}

public stop() {
this.stop$.next();
}
}