diff --git a/packages/kbn-reporting/public/job_completion_notifications.test.ts b/packages/kbn-reporting/public/job_completion_notifications.test.ts new file mode 100644 index 000000000000..6934bd5add40 --- /dev/null +++ b/packages/kbn-reporting/public/job_completion_notifications.test.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { jobCompletionNotifications } from './job_completion_notifications'; + +describe('Job completion notifications', () => { + const { setPendingJobIds, getPendingJobIds, addPendingJobId } = jobCompletionNotifications(); + + afterEach(async () => { + setPendingJobIds([]); + }); + + it('initially contains not job IDs', async () => { + expect(getPendingJobIds()).toEqual([]); + }); + + it('handles multiple job ID additions', async () => { + addPendingJobId('job-123'); + addPendingJobId('job-456'); + addPendingJobId('job-789'); + expect(getPendingJobIds()).toEqual(['job-123', 'job-456', 'job-789']); + }); + + it('handles setting a total of amount of job ID', async () => { + setPendingJobIds(['job-abc', 'job-def', 'job-ghi']); + expect(getPendingJobIds()).toEqual(['job-abc', 'job-def', 'job-ghi']); + }); + + it('able to clear all jobIds', async () => { + setPendingJobIds(['job-abc', 'job-def', 'job-ghi']); + setPendingJobIds([]); + expect(getPendingJobIds()).toEqual([]); + }); +}); diff --git a/packages/kbn-reporting/public/job_completion_notifications.ts b/packages/kbn-reporting/public/job_completion_notifications.ts index 4828caa30e54..c5f1d0f9b3ca 100644 --- a/packages/kbn-reporting/public/job_completion_notifications.ts +++ b/packages/kbn-reporting/public/job_completion_notifications.ts @@ -9,29 +9,44 @@ import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY } from '@kbn/reporting-common'; import { JobId } from '@kbn/reporting-common/types'; -const set = (jobs: string[]) => { - sessionStorage.setItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JSON.stringify(jobs)); -}; - -const getAll = (): string[] => { - const sessionValue = sessionStorage.getItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY); - return sessionValue ? JSON.parse(sessionValue) : []; -}; +export function jobCompletionNotifications() { + function getPendingJobIds(): JobId[] { + const jobs: JobId[] = []; + // get all current jobs + for (const key in localStorage) { + // check if key belongs to us + if (key.indexOf(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY) === 0) { + // get jobId from key + const jobId = key.replace(`${JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY}-`, ''); + jobs.push(jobId); + } + } + return jobs; + } -export const add = (jobId: JobId) => { - const jobs = getAll(); - jobs.push(jobId); - set(jobs); -}; + function addPendingJobId(jobId: JobId) { + // write back to local storage, value doesn't matter + localStorage.setItem(`${JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY}-${jobId}`, jobId); + } -export const remove = (jobId: JobId) => { - const jobs = getAll(); - const index = jobs.indexOf(jobId); + function setPendingJobIds(jobIds: JobId[]) { + // clear reporting jobIds + for (const key in localStorage) { + if (key.indexOf(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY) === 0) { + localStorage.removeItem(key); + } + } - if (!index) { - throw new Error('Unable to find job to remove it'); + // write update jobs back to local storage + for (let j = 0; j < jobIds.length; j++) { + const jobId = jobIds[j]; + localStorage.setItem(`${JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY}-${jobId}`, jobId); + } } - jobs.splice(index, 1); - set(jobs); -}; + return { + getPendingJobIds, + addPendingJobId, + setPendingJobIds, + }; +} diff --git a/packages/kbn-reporting/public/reporting_api_client.ts b/packages/kbn-reporting/public/reporting_api_client.ts index 135294230e29..9086a013f1c7 100644 --- a/packages/kbn-reporting/public/reporting_api_client.ts +++ b/packages/kbn-reporting/public/reporting_api_client.ts @@ -11,17 +11,18 @@ import type { HttpFetchQuery } from '@kbn/core/public'; import { HttpSetup, IUiSettingsClient } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { + INTERNAL_ROUTES, + PUBLIC_ROUTES, REPORTING_MANAGEMENT_HOME, buildKibanaPath, getRedirectAppPath, - INTERNAL_ROUTES, - PUBLIC_ROUTES, } from '@kbn/reporting-common'; import { BaseParams, JobId, ManagementLinkFn, ReportApiJSON } from '@kbn/reporting-common/types'; import rison from '@kbn/rison'; import moment from 'moment'; import { stringify } from 'query-string'; -import { Job, add } from '.'; +import { Job } from '.'; +import { jobCompletionNotifications } from './job_completion_notifications'; /* * For convenience, apps do not have to provide the browserTimezone and Kibana version. @@ -66,6 +67,7 @@ interface IReportingAPI { */ export class ReportingAPIClient implements IReportingAPI { private http: HttpSetup; + private addPendingJobId = jobCompletionNotifications().addPendingJobId; constructor( http: HttpSetup, @@ -182,7 +184,7 @@ export class ReportingAPIClient implements IReportingAPI { body: JSON.stringify({ jobParams: jobParamsRison }), } ); - add(resp.job.id); + this.addPendingJobId(resp.job.id); return new Job(resp.job); } diff --git a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap index ab6a5109a106..8f277f6d92c3 100644 --- a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap +++ b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap @@ -38,201 +38,209 @@ Object { exports[`stream handler showNotifications show csv formulas warning 1`] = ` Array [ - Object { - "data-test-subj": "completeReportCsvFormulasWarning", - "text": MountPoint { - "reactNode": -

- +

+ +

+

+ +

+ -

-

- -

- , + }, + "title": MountPoint { + "reactNode": -
, + />, + }, }, - "title": MountPoint { - "reactNode": , + Object { + "toastLifeTimeMs": 86400000, }, - }, - Object { - "toastLifeTimeMs": 86400000, - }, + ], ] `; exports[`stream handler showNotifications show failed job toast 1`] = ` Array [ - Object { - "data-test-subj": "completeReportFailure", - "iconType": undefined, - "text": MountPoint { - "reactNode": - - this is the failed report error - - -

- - - , + Array [ + Object { + "data-test-subj": "completeReportFailure", + "iconType": undefined, + "text": MountPoint { + "reactNode": + + this is the failed report error + + +

+ + + , + } } + /> +

+
, + }, + "title": MountPoint { + "reactNode": -

- , - }, - "title": MountPoint { - "reactNode": , + />, + }, }, - }, + ], ] `; exports[`stream handler showNotifications show max length warning 1`] = ` Array [ - Object { - "data-test-subj": "completeReportMaxSizeWarning", - "text": MountPoint { - "reactNode": -

- -

-

- +

+ +

+

+ +

+ -

- , + }, + "title": MountPoint { + "reactNode": -
, + />, + }, }, - "title": MountPoint { - "reactNode": , + Object { + "toastLifeTimeMs": 86400000, }, - }, - Object { - "toastLifeTimeMs": 86400000, - }, + ], ] `; exports[`stream handler showNotifications show success 1`] = ` Array [ - Object { - "color": "success", - "data-test-subj": "completeReportSuccess", - "text": MountPoint { - "reactNode": -

- +

+ +

+ -

- , + }, + "title": MountPoint { + "reactNode": -
, + />, + }, }, - "title": MountPoint { - "reactNode": , + Object { + "toastLifeTimeMs": 86400000, }, - }, - Object { - "toastLifeTimeMs": 86400000, - }, + ], ] `; diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 8cbe47e8b6e8..37ef7967ae28 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -5,22 +5,27 @@ * 2.0. */ -import sinon, { stub } from 'sinon'; - import { NotificationsStart } from '@kbn/core/public'; import { coreMock, docLinksServiceMock, themeServiceMock } from '@kbn/core/public/mocks'; -import { ReportApiJSON } from '@kbn/reporting-common/types'; +import { JobId, ReportApiJSON } from '@kbn/reporting-common/types'; -import { JobSummary } from '../types'; +import { JobSummary, JobSummarySet } from '../types'; import { Job, ReportingAPIClient } from '@kbn/reporting-public'; import { ReportingNotifierStreamHandler } from './stream_handler'; -Object.defineProperty(window, 'sessionStorage', { - value: { - setItem: jest.fn(() => null), - }, - writable: true, -}); +/** + * A test class that subclasses the main class with testable + * methods that access private methods indirectly. + */ +class TestReportingNotifierStreamHandler extends ReportingNotifierStreamHandler { + public testFindChangedStatusJobs(previousPending: JobId[]) { + return this.findChangedStatusJobs(previousPending); + } + + public testShowNotifications(jobs: JobSummarySet) { + return this.showNotifications(jobs); + } +} const mockJobsFound: Job[] = [ { id: 'job-source-mock1', status: 'completed', output: { csv_contains_formulas: false, max_size_reached: false }, payload: { title: 'specimen' } }, @@ -38,9 +43,9 @@ jobQueueClientMock.getError = () => Promise.resolve('this is the failed report e jobQueueClientMock.getManagementLink = () => '/#management'; jobQueueClientMock.getReportURL = () => '/reporting/download/job-123'; -const mockShowDanger = stub(); -const mockShowSuccess = stub(); -const mockShowWarning = stub(); +const mockShowDanger = jest.fn(); +const mockShowSuccess = jest.fn(); +const mockShowWarning = jest.fn(); const notificationsMock = { toasts: { addDanger: mockShowDanger, @@ -54,11 +59,11 @@ const docLink = docLinksServiceMock.createStartContract(); describe('stream handler', () => { afterEach(() => { - sinon.reset(); + jest.resetAllMocks(); }); it('constructs', () => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, @@ -69,13 +74,13 @@ describe('stream handler', () => { describe('findChangedStatusJobs', () => { it('finds no changed status jobs from empty', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - const findJobs = sh.findChangedStatusJobs([]); + const findJobs = sh.testFindChangedStatusJobs([]); findJobs.subscribe((data) => { expect(data).toEqual({ completed: [], failed: [] }); done(); @@ -83,13 +88,13 @@ describe('stream handler', () => { }); it('finds changed status jobs', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - const findJobs = sh.findChangedStatusJobs([ + const findJobs = sh.testFindChangedStatusJobs([ 'job-source-mock1', 'job-source-mock2', 'job-source-mock3', @@ -105,13 +110,13 @@ describe('stream handler', () => { describe('showNotifications', () => { it('show success', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - sh.showNotifications({ + sh.testShowNotifications({ completed: [ { id: 'yas1', @@ -122,22 +127,22 @@ describe('stream handler', () => { ], failed: [], }).subscribe(() => { - expect(mockShowDanger.callCount).toBe(0); - expect(mockShowSuccess.callCount).toBe(1); - expect(mockShowWarning.callCount).toBe(0); - expect(mockShowSuccess.args[0]).toMatchSnapshot(); + expect(mockShowDanger).not.toBeCalled(); + expect(mockShowSuccess).toBeCalledTimes(1); + expect(mockShowWarning).not.toBeCalled(); + expect(mockShowSuccess.mock.calls).toMatchSnapshot(); done(); }); }); it('show max length warning', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - sh.showNotifications({ + sh.testShowNotifications({ completed: [ { id: 'yas2', @@ -149,22 +154,22 @@ describe('stream handler', () => { ], failed: [], }).subscribe(() => { - expect(mockShowDanger.callCount).toBe(0); - expect(mockShowSuccess.callCount).toBe(0); - expect(mockShowWarning.callCount).toBe(1); - expect(mockShowWarning.args[0]).toMatchSnapshot(); + expect(mockShowDanger).not.toBeCalled(); + expect(mockShowSuccess).not.toBeCalled(); + expect(mockShowWarning).toBeCalledTimes(1); + expect(mockShowWarning.mock.calls).toMatchSnapshot(); done(); }); }); it('show csv formulas warning', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - sh.showNotifications({ + sh.testShowNotifications({ completed: [ { id: 'yas3', @@ -176,22 +181,22 @@ describe('stream handler', () => { ], failed: [], }).subscribe(() => { - expect(mockShowDanger.callCount).toBe(0); - expect(mockShowSuccess.callCount).toBe(0); - expect(mockShowWarning.callCount).toBe(1); - expect(mockShowWarning.args[0]).toMatchSnapshot(); + expect(mockShowDanger).not.toBeCalled(); + expect(mockShowSuccess).not.toBeCalled(); + expect(mockShowWarning).toBeCalledTimes(1); + expect(mockShowWarning.mock.calls).toMatchSnapshot(); done(); }); }); it('show failed job toast', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - sh.showNotifications({ + sh.testShowNotifications({ completed: [], failed: [ { @@ -202,22 +207,22 @@ describe('stream handler', () => { } as JobSummary, ], }).subscribe(() => { - expect(mockShowSuccess.callCount).toBe(0); - expect(mockShowWarning.callCount).toBe(0); - expect(mockShowDanger.callCount).toBe(1); - expect(mockShowDanger.args[0]).toMatchSnapshot(); + expect(mockShowSuccess).not.toBeCalled(); + expect(mockShowWarning).not.toBeCalled(); + expect(mockShowDanger).toBeCalledTimes(1); + expect(mockShowDanger.mock.calls).toMatchSnapshot(); done(); }); }); it('show multiple toast', (done) => { - const sh = new ReportingNotifierStreamHandler( + const sh = new TestReportingNotifierStreamHandler( notificationsMock, jobQueueClientMock, theme, docLink ); - sh.showNotifications({ + sh.testShowNotifications({ completed: [ { id: 'yas8', @@ -249,9 +254,9 @@ describe('stream handler', () => { } as JobSummary, ], }).subscribe(() => { - expect(mockShowSuccess.callCount).toBe(1); - expect(mockShowWarning.callCount).toBe(2); - expect(mockShowDanger.callCount).toBe(1); + expect(mockShowSuccess).toBeCalledTimes(1); + expect(mockShowWarning).toBeCalledTimes(2); + expect(mockShowDanger).toBeCalledTimes(1); done(); }); }); diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 8b35485dd7f9..2a7124e1e5e3 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -6,14 +6,14 @@ */ import * as Rx from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; +import { catchError, filter, map, mergeMap, takeUntil } from 'rxjs/operators'; import { DocLinksStart, NotificationsSetup, ThemeServiceStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JOB_STATUS } from '@kbn/reporting-common'; +import { JOB_STATUS } from '@kbn/reporting-common'; import { JobId } from '@kbn/reporting-common/types'; -import { Job, ReportingAPIClient } from '@kbn/reporting-public'; +import { Job, ReportingAPIClient, jobCompletionNotifications } from '@kbn/reporting-public'; import { getFailureToast, getGeneralErrorToast, @@ -30,10 +30,6 @@ import { JobSummary, JobSummarySet } from '../types'; */ const COMPLETED_JOB_TOAST_TIMEOUT = 24 * 60 * 60 * 1000; // 24 hours -function updateStored(jobIds: JobId[]): void { - sessionStorage.setItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JSON.stringify(jobIds)); -} - function getReportStatus(src: Job): JobSummary { return { id: src.id, @@ -46,7 +42,27 @@ function getReportStatus(src: Job): JobSummary { }; } +function handleError( + err: Error, + notifications: NotificationsSetup, + theme: ThemeServiceStart +): Rx.Observable { + notifications.toasts.addDanger( + getGeneralErrorToast( + i18n.translate('xpack.reporting.publicNotifier.pollingErrorMessage', { + defaultMessage: 'Reporting notifier error!', + }), + err, + theme + ) + ); + window.console.error(err); + return Rx.of({ completed: [], failed: [] }); +} + export class ReportingNotifierStreamHandler { + private jobCompletionNotifications = jobCompletionNotifications(); + constructor( private notifications: NotificationsSetup, private apiClient: ReportingAPIClient, @@ -54,72 +70,70 @@ export class ReportingNotifierStreamHandler { private docLinks: DocLinksStart ) {} + public startPolling(interval: number, stop$: Rx.Observable) { + Rx.timer(0, interval) + .pipe( + takeUntil(stop$), // stop the interval when stop method is called + map(this.jobCompletionNotifications.getPendingJobIds), // read all pending job IDs from session storage + filter((previousPending) => previousPending.length > 0), // stop the pipeline here if there are none pending + mergeMap((previousPending) => this.findChangedStatusJobs(previousPending)), // look up the latest status of all pending jobs on the server + mergeMap(({ completed, failed }) => this.showNotifications({ completed, failed })), + catchError((err) => { + // eslint-disable-next-line no-console + console.error(err); + return handleError(err, this.notifications, this.theme); + }) + ) + .subscribe(); + } + /* * Use Kibana Toast API to show our messages */ - public showNotifications({ + protected showNotifications({ completed: completedJobs, failed: failedJobs, }: JobSummarySet): Rx.Observable { + const notifications = this.notifications; + const apiClient = this.apiClient; + const theme = this.theme; + const docLinks = this.docLinks; + const getManagementLink = apiClient.getManagementLink.bind(apiClient); + const getDownloadLink = apiClient.getDownloadLink.bind(apiClient); + const showNotificationsAsync = async () => { const completedOptions = { toastLifeTimeMs: COMPLETED_JOB_TOAST_TIMEOUT }; // notifications with download link - for (const job of completedJobs) { + for (const job of completedJobs ?? []) { if (job.csvContainsFormulas) { - this.notifications.toasts.addWarning( - getWarningFormulasToast( - job, - this.apiClient.getManagementLink, - this.apiClient.getDownloadLink, - this.theme - ), + notifications.toasts.addWarning( + getWarningFormulasToast(job, getManagementLink, getDownloadLink, theme), completedOptions ); } else if (job.maxSizeReached) { - this.notifications.toasts.addWarning( - getWarningMaxSizeToast( - job, - this.apiClient.getManagementLink, - this.apiClient.getDownloadLink, - this.theme - ), + notifications.toasts.addWarning( + getWarningMaxSizeToast(job, getManagementLink, getDownloadLink, theme), completedOptions ); } else if (job.status === JOB_STATUS.WARNINGS) { - this.notifications.toasts.addWarning( - getWarningToast( - job, - this.apiClient.getManagementLink, - this.apiClient.getDownloadLink, - this.theme - ), + notifications.toasts.addWarning( + getWarningToast(job, getManagementLink, getDownloadLink, theme), completedOptions ); } else { - this.notifications.toasts.addSuccess( - getSuccessToast( - job, - this.apiClient.getManagementLink, - this.apiClient.getDownloadLink, - this.theme - ), + notifications.toasts.addSuccess( + getSuccessToast(job, getManagementLink, getDownloadLink, theme), completedOptions ); } } // no download link available - for (const job of failedJobs) { - const errorText = await this.apiClient.getError(job.id); + for (const job of failedJobs ?? []) { + const errorText = await apiClient.getError(job.id); this.notifications.toasts.addDanger( - getFailureToast( - errorText, - job, - this.apiClient.getManagementLink, - this.theme, - this.docLinks - ) + getFailureToast(errorText, job, getManagementLink, theme, docLinks) ); } return { completed: completedJobs, failed: failedJobs }; @@ -132,29 +146,35 @@ export class ReportingNotifierStreamHandler { * An observable that finds jobs that are known to be "processing" (stored in * session storage) but have non-processing job status on the server */ - public findChangedStatusJobs(storedJobs: JobId[]): Rx.Observable { - return Rx.from(this.apiClient.findForJobIds(storedJobs)).pipe( - map((jobs) => { - const completedJobs: JobSummary[] = []; - const failedJobs: JobSummary[] = []; - const pending: JobId[] = []; - - // add side effects to storage - for (const job of jobs) { - const { id: jobId, status: jobStatus } = job; - if (storedJobs.includes(jobId)) { - if (jobStatus === JOB_STATUS.COMPLETED || jobStatus === JOB_STATUS.WARNINGS) { - completedJobs.push(getReportStatus(job)); - } else if (jobStatus === JOB_STATUS.FAILED) { - failedJobs.push(getReportStatus(job)); - } else { - pending.push(jobId); - } + protected findChangedStatusJobs(previousPending: JobId[]): Rx.Observable { + return Rx.from(this.apiClient.findForJobIds(previousPending)).pipe( + mergeMap(async (jobs) => { + const newCompleted: JobSummary[] = []; + const newFailed: JobSummary[] = []; + const newPending: JobId[] = []; + + for (const pendingJobId of previousPending) { + const updatedJob = jobs.find(({ id }) => id === pendingJobId); + if ( + updatedJob?.status === JOB_STATUS.COMPLETED || + updatedJob?.status === JOB_STATUS.WARNINGS + ) { + newCompleted.push(getReportStatus(updatedJob)); + } else if (updatedJob?.status === JOB_STATUS.FAILED) { + newFailed.push(getReportStatus(updatedJob)); + } else { + // Keep job tracked in storage if is pending. It also + // may not be present in apiClient.findForJobIds + // response if index refresh is slow + newPending.push(pendingJobId); } } - updateStored(pending); // refresh the storage of pending job IDs, minus completed and failed job IDs - return { completed: completedJobs, failed: failedJobs }; + // refresh the storage of pending job IDs, minus + // the newly completed and failed jobs + this.jobCompletionNotifications.setPendingJobIds(newPending); + + return { completed: newCompleted, failed: newFailed }; }), catchError((err) => { // show connection refused toast @@ -166,9 +186,9 @@ export class ReportingNotifierStreamHandler { err, this.theme ) - ); // prettier-ignore + ); window.console.error(err); - return Rx.of({ completed: [], failed: [] }); // log the error and resume + return Rx.of({}); }) ); } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index edcc2affaa3d..5f58d5f115c5 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -6,17 +6,14 @@ */ import * as Rx from 'rxjs'; -import { catchError, filter, map, mergeMap, takeUntil } from 'rxjs/operators'; import { CoreSetup, CoreStart, HttpSetup, IUiSettingsClient, - NotificationsSetup, Plugin, PluginInitializerContext, - ThemeServiceStart, } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; @@ -26,45 +23,20 @@ import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/public'; -import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, durationToNumber } from '@kbn/reporting-common'; -import type { JobId } from '@kbn/reporting-common/types'; -import { ClientConfigType, ReportingAPIClient } from '@kbn/reporting-public'; +import { durationToNumber } from '@kbn/reporting-common'; +import type { ClientConfigType } from '@kbn/reporting-public'; +import { ReportingAPIClient } from '@kbn/reporting-public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { getSharedComponents, - reportingScreenshotShareProvider, reportingCsvShareProvider, + reportingScreenshotShareProvider, } from '@kbn/reporting-public/share'; import type { ReportingSetup, ReportingStart } from '.'; import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler'; -import { getGeneralErrorToast } from './notifier'; import { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action'; -import type { JobSummarySet } from './types'; - -function getStored(): JobId[] { - const sessionValue = sessionStorage.getItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY); - return sessionValue ? JSON.parse(sessionValue) : []; -} - -function handleError( - notifications: NotificationsSetup, - err: Error, - theme: ThemeServiceStart -): Rx.Observable { - notifications.toasts.addDanger( - getGeneralErrorToast( - i18n.translate('xpack.reporting.publicNotifier.pollingErrorMessage', { - defaultMessage: 'Reporting notifier error!', - }), - err, - theme - ) - ); - window.console.error(err); - return Rx.of({ completed: [], failed: [] }); -} export interface ReportingPublicPluginSetupDependencies { home: HomePublicPluginSetup; @@ -265,16 +237,7 @@ export class ReportingPublicPlugin const apiClient = this.getApiClient(core.http, core.uiSettings); const streamHandler = new StreamHandler(notifications, apiClient, core.theme, docLinks); const interval = durationToNumber(this.config.poll.jobsRefresh.interval); - Rx.timer(0, interval) - .pipe( - takeUntil(this.stop$), // stop the interval when stop method is called - map(() => getStored()), // read all pending job IDs from session storage - filter((storedJobs) => storedJobs.length > 0), // stop the pipeline here if there are none pending - mergeMap((storedJobs) => streamHandler.findChangedStatusJobs(storedJobs)), // look up the latest status of all pending jobs on the server - mergeMap(({ completed, failed }) => streamHandler.showNotifications({ completed, failed })), - catchError((err) => handleError(notifications, err, core.theme)) - ) - .subscribe(); + streamHandler.startPolling(interval, this.stop$); return this.getContract(); } diff --git a/x-pack/plugins/reporting/public/types.ts b/x-pack/plugins/reporting/public/types.ts index dcc887ef9a84..d5af032db617 100644 --- a/x-pack/plugins/reporting/public/types.ts +++ b/x-pack/plugins/reporting/public/types.ts @@ -27,6 +27,6 @@ export interface JobSummary { * @internal */ export interface JobSummarySet { - completed: JobSummary[]; - failed: JobSummary[]; + completed?: JobSummary[]; + failed?: JobSummary[]; }