Skip to content
Closed
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
47 changes: 44 additions & 3 deletions src/plugins/newsfeed/public/components/flyout_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useCallback, useContext } from 'react';
import React, { useCallback, useContext, useState, useEffect } from 'react';
import {
EuiIcon,
EuiFlyout,
Expand All @@ -38,17 +38,27 @@ import { PulseChannel } from 'src/core/public/pulse/channel';
import { NotificationInstruction } from 'src/core/server/pulse/collectors/notifications';
import moment from 'moment';
import { EuiHeaderAlert } from '../../../../legacy/core_plugins/newsfeed/public/np_ready/components/header_alert/header_alert';
import { NewsfeedContext, shouldUpdateHash, getLastItemHash } from './newsfeed_header_nav_button';
import {
NewsfeedContext,
shouldUpdateHash,
getLastItemHash,
ErrorFixedVersionsInstructions,
} from './newsfeed_header_nav_button';
import { NewsfeedItem } from '../../types';
import { NewsEmptyPrompt } from './empty_news';
import { NewsLoadingPrompt } from './loading_news';
import { PulseErrorsInstruction } from '../plugin';

interface Props {
notificationsChannel: PulseChannel<NotificationInstruction>;
errorsInstructionsToShow: ErrorFixedVersionsInstructions[];
}

export const NewsfeedFlyout = ({ notificationsChannel }: Props) => {
export const NewsfeedFlyout = ({ notificationsChannel, errorsInstructionsToShow }: Props) => {
const { newsFetchResult, setFlyoutVisible } = useContext(NewsfeedContext);
const [errorsInstructionsReceivedResult, setErrorsInstructionsReceivedResult] = useState(
[] as any
);
const closeFlyout = useCallback(() => setFlyoutVisible(false), [setFlyoutVisible]);

if (newsFetchResult && newsFetchResult.feedItems.length) {
Expand All @@ -70,6 +80,15 @@ export const NewsfeedFlyout = ({ notificationsChannel }: Props) => {
}
}

useEffect(() => {
function handleErrorsInstructionsReceivedChange(items: any[]) {
setErrorsInstructionsReceivedResult(items);
}
if (errorsInstructionsToShow && errorsInstructionsToShow.length) {
return handleErrorsInstructionsReceivedChange(errorsInstructionsToShow);
}
}, [errorsInstructionsToShow]);

return (
<EuiFlyout
onClose={closeFlyout}
Expand Down Expand Up @@ -110,6 +129,28 @@ export const NewsfeedFlyout = ({ notificationsChannel }: Props) => {
) : (
<NewsEmptyPrompt />
)}
{!errorsInstructionsReceivedResult.length ? (
<div>No instructions from Pulse</div>
) : (
errorsInstructionsReceivedResult.map((item: PulseErrorsInstruction, index: number) => {
return (
<EuiHeaderAlert
key={index}
title={item.hash}
text={item.pulseMessage}
data-test-subj="newsHeadAlert"
action={
<EuiLink target="_blank" href={'#'}>
{item.fixedVersion}
<EuiIcon type="popout" size="s" />
</EuiLink>
}
date={moment(item.timestamp).format('DD MMMM YYYY')}
badge={<EuiBadge color="hollow">{item.fixedVersion}</EuiBadge>}
/>
);
})
)}
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import React, { useState, Fragment, useEffect } from 'react';
import * as Rx from 'rxjs';
import { EuiHeaderSectionItemButton, EuiIcon, EuiNotificationBadge } from '@elastic/eui';
// eslint-disable-next-line
import { PulseChannel } from 'src/core/public/pulse/channel';
import { PulseChannel, PulseInstruction } from 'src/core/public/pulse/channel';
// eslint-disable-next-line
import { NotificationInstruction } from 'src/core/server/pulse/collectors/notifications';
import moment from 'moment';
Expand All @@ -39,6 +39,7 @@ export type NewsfeedApiFetchResult = Rx.Observable<void | FetchResult | null>;
export interface Props {
apiFetchResult: NewsfeedApiFetchResult;
notificationsChannel: PulseChannel<NotificationInstruction>;
errorsInstructions?: PulseInstruction[];
}

const NEWSFEED_LAST_HASH = 'pulse_news_last_hash';
Expand All @@ -58,6 +59,7 @@ export function shouldUpdateHash(lastNotificationHash: string): boolean {
export function getLastItemHash(instructions: Array<{ hash: string }>) {
return instructions[instructions.length - 1].hash;
}

/**
window.notificationsChannel.sendPulse([{
"hash": "test_hash",
Expand All @@ -71,14 +73,24 @@ window.notificationsChannel.sendPulse([{
"status": "new",
}])
*/

export interface ErrorFixedVersionsInstructions {
[key: string]: any;
}
// on every fresh page reload, fetch news all over again.
updateLastHash('');

export const NewsfeedNavButton = ({ apiFetchResult, notificationsChannel }: Props) => {
export const NewsfeedNavButton = ({
apiFetchResult,
notificationsChannel,
errorsInstructions,
}: Props) => {
const [showBadge, setShowBadge] = useState<boolean>(false);
const [flyoutVisible, setFlyoutVisible] = useState<boolean>(false);
const [newsFetchResult, setNewsFetchResult] = useState<FetchResult | null | void>(null);
const [showErrorsBadge, setShowErrorsBadge] = useState<boolean>(false);
const [errorsInstructionsReceivedResult, setErrorsInstructionsReceivedResult] = useState(
[] as any
);
// hack to test updating news;
(window as any).moment = moment;
(window as any).notificationsChannel = notificationsChannel;
Expand Down Expand Up @@ -114,7 +126,15 @@ export const NewsfeedNavButton = ({ apiFetchResult, notificationsChannel }: Prop
});
return () => subscription.unsubscribe();
}, [notificationsInstructions$]);

useEffect(() => {
function handleErrorsInstructionsReceivedChange(items: any[]) {
setErrorsInstructionsReceivedResult(items);
setShowErrorsBadge(true);
}
if (errorsInstructions && errorsInstructions.length) {
return handleErrorsInstructionsReceivedChange(errorsInstructions);
}
}, [errorsInstructions]);
// FOR THE POC: JUST USE PULSE AS THE SOURCE OF TRUTH FOR NEWS!
// useEffect(() => {
// function handleStatusChange(fetchResult: FetchResult | void | null) {
Expand Down Expand Up @@ -151,8 +171,18 @@ export const NewsfeedNavButton = ({ apiFetchResult, notificationsChannel }: Prop
&#9642;
</EuiNotificationBadge>
) : null}
{showErrorsBadge ? (
<EuiNotificationBadge className="euiHeaderNotification" data-test-subj="showBadgeNews">
P {errorsInstructionsReceivedResult.length};
</EuiNotificationBadge>
) : null}
</EuiHeaderSectionItemButton>
{flyoutVisible ? <NewsfeedFlyout notificationsChannel={notificationsChannel} /> : null}
{flyoutVisible ? (
<NewsfeedFlyout
notificationsChannel={notificationsChannel}
errorsInstructionsToShow={errorsInstructions as any[]}
/>
) : null}
</Fragment>
</NewsfeedContext.Provider>
);
Expand Down
28 changes: 23 additions & 5 deletions src/plugins/newsfeed/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,27 @@ import ReactDOM from 'react-dom';
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
// eslint-disable-next-line
import { PulseChannel } from 'src/core/server/pulse/channel';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { PulseChannel } from 'src/core/public/pulse/channel';
import { NewsfeedPluginInjectedConfig } from '../types';
import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button';
import { getApi } from './lib/api';

export type Setup = void;
export type Start = void;
export type Start = ReturnType<NewsfeedPublicPlugin['start']>;

export interface PulseErrorsInstruction {
pulseMessage: string;
sendTo: string;
timestamp?: Date;
hash: string;
fixedVersion: string;
}
export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
private readonly kibanaVersion: string;
private readonly stop$ = new Rx.ReplaySubject(1);
private notificationsChannel?: PulseChannel;
private readonly pulseErrorsInstructions?: PulseErrorsInstruction[] = [];

constructor(initializerContext: PluginInitializerContext) {
this.kibanaVersion = initializerContext.env.packageInfo.version;
Expand All @@ -45,12 +53,18 @@ export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {
this.notificationsChannel = core.pulse.getChannel('notifications');
}

public start(core: CoreStart): Start {
public start(core: CoreStart) {
const api$ = this.fetchNewsfeed(core);
core.chrome.navControls.registerRight({
order: 1000,
mount: target => this.mount(api$, target),
});

return {
addPulseErrorsInstructions: (entry: PulseErrorsInstruction) => {
this.pulseErrorsInstructions!.push(entry);
},
};
}

public stop() {
Expand All @@ -76,7 +90,11 @@ export class NewsfeedPublicPlugin implements Plugin<Setup, Start> {

ReactDOM.render(
<I18nProvider>
<NewsfeedNavButton apiFetchResult={api$} notificationsChannel={this.notificationsChannel} />
<NewsfeedNavButton
apiFetchResult={api$}
notificationsChannel={this.notificationsChannel as any}
errorsInstructions={this.pulseErrorsInstructions}
/>
</I18nProvider>,
targetDomElement
);
Expand Down
1 change: 1 addition & 0 deletions src/plugins/pulse_errors/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"pulse_errors"
],
"server": true,
"requiredPlugins": ["newsfeed"],
"ui": true
}
6 changes: 3 additions & 3 deletions src/plugins/pulse_errors/public/mock_data/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const errorChannelPayloads: PulseErrorPayloadRecord[] = [
fixedVersion: 'v7.5.2',
currentKibanaVersion: 'v7.x',
timestamp: moment()
.add(60, 'seconds')
.add(10, 'seconds')
.toDate(),
},
{
Expand All @@ -60,10 +60,10 @@ export const errorChannelPayloads: PulseErrorPayloadRecord[] = [
message: '[Error]: Test',
hash: '[plugins][pulse_errors]: [Error]: fakeError:arbitraryError 2',
status: 'new',
// fixedVersion: 'v7.5.2',
fixedVersion: 'v7.5.2',
currentKibanaVersion: 'v7.x',
timestamp: moment()
.add(90, 'seconds')
.add(20, 'seconds')
.toDate(),
},
];
41 changes: 27 additions & 14 deletions src/plugins/pulse_errors/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,51 @@ import { Plugin, CoreSetup, CoreStart } from 'kibana/public';
import { Subject, Subscription } from 'rxjs';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { takeUntil } from 'rxjs/operators';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { Start } from 'src/plugins/newsfeed/public/plugin';
import { errorChannelPayloads } from './mock_data/errors';

export class PulseErrorsPlugin implements Plugin<PulseErrorsPluginSetup, PulseErrorsPluginStart> {
private readonly stop$ = new Subject();
private instructionsSubscription?: Subscription;
private instructionsSeen = new Set(); // TODO: possibly change this to a map later to store more detailed info.
private noFixedVersionsSeen = new Set(); // TODO: possibly change this to a map later to store more detailed info.
private fixedVersionsSeen = new Set(); // TODO: possibly change this to a map later to store more detailed info.
constructor() {}

public async setup(core: CoreSetup) {
errorChannelPayloads.forEach(element => core.pulse.getChannel('errors').sendPulse(element));
}

public start(core: CoreStart) {
public start(core: CoreStart, { newsfeed }: { newsfeed: Start }) {
this.instructionsSubscription = core.pulse
.getChannel('errors')
.instructions$()
.pipe(takeUntil(this.stop$))
.subscribe(instructions => {
if (instructions && instructions.length) {
instructions.forEach((instruction: any) => {
// @ts-ignore-next-line this should be refering to the instruction, not the raw es document
if (
instruction &&
instruction.status === 'new' &&
!this.instructionsSeen.has(instruction.hash)
)
core.notifications.toasts.addError(new Error(JSON.stringify(instruction)), {
// @ts-ignore-next-line
title: `Error:${instruction.hash}`,
toastMessage: `An error occurred: ${instruction.message}. Pulse message:${instruction.pulseMessage}`,
});
this.instructionsSeen.add(instruction.hash);
// general conditional for existance of instructions
if (instruction) {
// condition to send instructions to toasts
if (
instruction.sendTo === 'toasts' &&
!this.noFixedVersionsSeen.has(instruction.hash)
) {
core.notifications.toasts.addError(new Error(JSON.stringify(instruction)), {
// @ts-ignore-next-line
title: `Error:${instruction.hash}`,
toastMessage: `An error occurred: ${instruction.message}. Pulse message:${instruction.pulseMessage}`,
});
this.noFixedVersionsSeen.add(instruction.hash);
}
// condition to send instructions to newsfeed
if (
instruction.sendTo === 'newsfeed' &&
!this.fixedVersionsSeen.has(instruction.hash)
) {
newsfeed.addPulseErrorsInstructions(instruction);
}
}
});
}
});
Expand Down