Migrate report listing management to react and EUI (#22928)

* EUIify report management page

* wire ReportListing component together

* fetch jobs and display content in EuiPage

* display jobs in table

* add title and remove page size dropdown

* format date and display date in status column

* add poller

* add download button

* report error button

* remove old reporting table

* fix page styling

* create type for job

* remove job queue service

* remove angular-paging dependency from x-pack

* make download lib, update job notification hack to use jobQueueClient

* fix some more typescript stuff

* remove last angular service

* make report object type subdued color and small text

* update import in canvas

* stricter typing

* fix stuff lost in branch merge

* add return types to JobQueueClient

* wrap javascript code in {} in JSX
This commit is contained in:
Nathan Reese 2018-09-27 14:49:41 -06:00 committed by GitHub
parent 0ff498d5c4
commit 95f48c584e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 526 additions and 326 deletions

View file

@ -28,6 +28,7 @@ declare class Chrome {
public getXsrfToken(): string;
public getKibanaVersion(): string;
public getUiSettingsClient(): any;
public getInjected(key: string, defaultValue?: any): any;
}
declare const chrome: Chrome;

View file

@ -4,10 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable no-var */
require('jquery');
require('angular');
require('angular-paging/dist/paging');
export declare class Poller {
constructor(options: any);
var uiModules = require('ui/modules').uiModules;
uiModules.get('kibana', ['bw.paging']);
public start(): void;
}

View file

@ -98,7 +98,6 @@
"@scant/router": "^0.1.0",
"@slack/client": "^4.2.2",
"@types/moment-timezone": "^0.5.8",
"angular-paging": "2.2.1",
"angular-resource": "1.4.9",
"angular-sanitize": "1.4.9",
"angular-ui-ace": "0.2.3",

View file

@ -6,7 +6,7 @@
/* eslint import/no-unresolved: 1 */
// TODO: remove eslint rule when updating to use the linked kibana resolve package
import { jobCompletionNotifications } from 'plugins/reporting/services/job_completion_notifications';
import { jobCompletionNotifications } from 'plugins/reporting/lib/job_completion_notifications';
import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
import { getWorkpad, getPages } from '../../state/selectors/workpad';

View file

@ -6,11 +6,12 @@
export const QUEUE_DOCTYPE = 'esqueue';
export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY = 'xpack.reporting.jobCompletionNotifications';
export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY =
'xpack.reporting.jobCompletionNotifications';
export const API_BASE_URL = '/api/reporting';
export const WHITELISTED_JOB_CONTENT_TYPES = [ 'application/json', 'application/pdf', 'text/csv' ];
export const WHITELISTED_JOB_CONTENT_TYPES = ['application/json', 'application/pdf', 'text/csv'];
export const UI_SETTINGS_CUSTOM_PDF_LOGO = 'xpackReporting:customPdfLogo';

View file

@ -0,0 +1,99 @@
/*
* 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 { EuiButtonIcon, EuiCallOut, EuiPopover } from '@elastic/eui';
import React, { Component } from 'react';
import { JobContent, jobQueueClient } from '../lib/job_queue_client';
interface Props {
jobId: string;
}
interface State {
isLoading: boolean;
isPopoverOpen: boolean;
calloutTitle: string;
error?: string;
}
export class ReportErrorButton extends Component<Props, State> {
private mounted?: boolean;
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
isPopoverOpen: false,
calloutTitle: 'Unable to generate report',
};
}
public render() {
const button = (
<EuiButtonIcon
onClick={this.togglePopover}
iconType="alert"
color={'danger'}
aria-label="Show report error"
/>
);
return (
<EuiPopover
id="popover"
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
anchorPosition="downRight"
>
<EuiCallOut color="danger" title={this.state.calloutTitle}>
<p>{this.state.error}</p>
</EuiCallOut>
</EuiPopover>
);
}
public componentWillUnmount() {
this.mounted = false;
}
public componentDidMount() {
this.mounted = true;
}
private togglePopover = () => {
this.setState(prevState => {
return { isPopoverOpen: !prevState.isPopoverOpen };
});
if (!this.state.error) {
this.loadError();
}
};
private closePopover = () => {
this.setState({ isPopoverOpen: false });
};
private loadError = async () => {
this.setState({ isLoading: true });
try {
const reportContent: JobContent = await jobQueueClient.getContent(this.props.jobId);
if (this.mounted) {
this.setState({ isLoading: false, error: reportContent.content });
}
} catch (kfetchError) {
if (this.mounted) {
this.setState({
isLoading: false,
calloutTitle: 'Unable to fetch report content',
error: kfetchError.message,
});
}
}
};
}

View file

@ -0,0 +1,311 @@
/*
* 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.
*/
// TODO: Remove once typescript definitions are in EUI
declare module '@elastic/eui' {
export const EuiBasicTable: React.SFC<any>;
export const EuiTextColor: React.SFC<any>;
}
import moment from 'moment';
import React, { Component } from 'react';
import chrome from 'ui/chrome';
import { toastNotifications } from 'ui/notify';
import { Poller } from '../../../../common/poller';
import { downloadReport } from '../lib/download_report';
import { jobQueueClient, JobQueueEntry } from '../lib/job_queue_client';
import { ReportErrorButton } from './report_error_button';
import {
EuiBasicTable,
EuiButtonIcon,
EuiPage,
EuiPageBody,
EuiPageContent,
EuiText,
EuiTextColor,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
interface Job {
id: string;
type: string;
object_type: string;
object_title: string;
created_by?: string;
created_at: string;
started_at?: string;
completed_at?: string;
status: string;
max_size_reached: boolean;
}
interface Props {
badLicenseMessage: string;
showLinks: boolean;
enableLinks: boolean;
redirect: (url: string) => void;
}
interface State {
page: number;
total: number;
jobs: Job[];
isLoading: boolean;
}
export class ReportListing extends Component<Props, State> {
private mounted?: boolean;
private poller?: any;
private isInitialJobsFetch: boolean;
constructor(props: Props) {
super(props);
this.state = {
page: 0,
total: 0,
jobs: [],
isLoading: false,
};
this.isInitialJobsFetch = true;
}
public render() {
return (
<EuiPage className="repReportListing__page">
<EuiPageBody restrictWidth>
<EuiPageContent horizontalPosition="center">
<EuiTitle>
<h1>Reports</h1>
</EuiTitle>
{this.renderTable()}
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
public componentWillUnmount() {
this.mounted = false;
this.poller.stop();
}
public componentDidMount() {
this.mounted = true;
const { jobsRefresh } = chrome.getInjected('reportingPollConfig');
this.poller = new Poller({
functionToPoll: () => {
return this.fetchJobs();
},
pollFrequencyInMillis: jobsRefresh.interval,
trailing: false,
continuePollingOnError: true,
pollFrequencyErrorMultiplier: jobsRefresh.intervalErrorMultiplier,
});
this.poller.start();
}
private renderTable() {
const tableColumns = [
{
field: 'object_title',
name: 'Report',
render: (objectTitle: string, record: Job) => {
return (
<div>
<div>{objectTitle}</div>
<EuiText size="s">
<EuiTextColor color="subdued">{record.object_type}</EuiTextColor>
</EuiText>
</div>
);
},
},
{
field: 'created_at',
name: 'Created at',
render: (createdAt: string, record: Job) => {
if (record.created_by) {
return (
<div>
<div>{this.formatDate(createdAt)}</div>
<span>{record.created_by}</span>
</div>
);
}
return this.formatDate(createdAt);
},
},
{
field: 'status',
name: 'Status',
render: (status: string, record: Job) => {
let maxSizeReached;
if (record.max_size_reached) {
maxSizeReached = <span> - max size reached</span>;
}
let statusTimestamp;
if (status === 'processing' && record.started_at) {
statusTimestamp = this.formatDate(record.started_at);
} else if (record.completed_at && (status === 'completed' || status === 'failed')) {
statusTimestamp = this.formatDate(record.completed_at);
}
return (
<div>
{status}
{' at '}
<span className="eui-textNoWrap">{statusTimestamp}</span>
{maxSizeReached}
</div>
);
},
},
{
name: 'Actions',
actions: [
{
render: (record: Job) => {
return (
<div>
{this.renderDownloadButton(record)}
{this.renderReportErrorButton(record)}
</div>
);
},
},
],
},
];
const pagination = {
pageIndex: this.state.page,
pageSize: 10,
totalItemCount: this.state.total,
hidePerPageOptions: true,
};
return (
<EuiBasicTable
itemId={'id'}
items={this.state.jobs}
loading={this.state.isLoading}
columns={tableColumns}
noItemsMessage={this.state.isLoading ? 'Loading reports' : 'No reports have been created'}
pagination={pagination}
onChange={this.onTableChange}
/>
);
}
private renderDownloadButton = (record: Job) => {
if (record.status !== 'completed') {
return;
}
const button = (
<EuiButtonIcon
onClick={() => downloadReport(record.id)}
iconType="importAction"
aria-label="Download report"
/>
);
if (record.max_size_reached) {
return (
<EuiToolTip position="top" content="Max size reached, contains partial data.">
{button}
</EuiToolTip>
);
}
return button;
};
private renderReportErrorButton = (record: Job) => {
if (record.status !== 'failed') {
return;
}
return <ReportErrorButton jobId={record.id} />;
};
private onTableChange = ({ page }: { page: { index: number } }) => {
const { index: pageIndex } = page;
this.setState(
{
page: pageIndex,
},
this.fetchJobs
);
};
private fetchJobs = async () => {
// avoid page flicker when poller is updating table - only display loading screen on first load
if (this.isInitialJobsFetch) {
this.setState({ isLoading: true });
}
let jobs: JobQueueEntry[];
let total: number;
try {
jobs = await jobQueueClient.list(this.state.page);
total = await jobQueueClient.total();
this.isInitialJobsFetch = false;
} catch (kfetchError) {
if (!this.licenseAllowsToShowThisPage()) {
toastNotifications.addDanger(this.props.badLicenseMessage);
this.props.redirect('/management');
return;
}
if (kfetchError.res.status !== 401 && kfetchError.res.status !== 403) {
toastNotifications.addDanger(kfetchError.res.statusText || 'Request failed');
}
if (this.mounted) {
this.setState({ isLoading: false, jobs: [], total: 0 });
}
return;
}
if (this.mounted) {
this.setState({
isLoading: false,
total,
jobs: jobs.map((job: JobQueueEntry) => {
return {
id: job._id,
type: job._source.jobtype,
object_type: job._source.payload.type,
object_title: job._source.payload.title,
created_by: job._source.created_by,
created_at: job._source.created_at,
started_at: job._source.started_at,
completed_at: job._source.completed_at,
status: job._source.status,
max_size_reached: job._source.output ? job._source.output.max_size_reached : false,
};
}),
});
}
};
private licenseAllowsToShowThisPage = () => {
return this.props.showLinks && this.props.enableLinks;
};
private formatDate(timestamp: string) {
try {
return moment(timestamp).format('YYYY-MM-DD @ hh:mm A');
} catch (error) {
// ignore parse error and display unformatted value
return timestamp;
}
}
}

View file

@ -8,25 +8,22 @@ import React from 'react';
import { toastNotifications } from 'ui/notify';
import chrome from 'ui/chrome';
import { uiModules } from 'ui/modules';
import { addSystemApiHeader } from 'ui/system_api';
import { get } from 'lodash';
import {
API_BASE_URL
} from '../../common/constants';
import 'plugins/reporting/services/job_queue';
import 'plugins/reporting/services/job_completion_notifications';
import { jobQueueClient } from 'plugins/reporting/lib/job_queue_client';
import { jobCompletionNotifications } from 'plugins/reporting/lib/job_completion_notifications';
import { PathProvider } from 'plugins/xpack_main/services/path';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import { Poller } from '../../../../common/poller';
import {
EuiButton,
} from '@elastic/eui';
import { downloadReport } from '../lib/download_report';
/**
* Poll for changes to reports. Inform the user of changes when the license is active.
*/
uiModules.get('kibana')
.run(($http, reportingJobQueue, Private, reportingPollConfig, reportingJobCompletionNotifications) => {
.run((Private, reportingPollConfig) => {
// Don't show users any reporting toasts until they're logged in.
if (Private(PathProvider).isLoginOrLogout()) {
return;
@ -44,7 +41,7 @@ uiModules.get('kibana')
const isJobSuccessful = get(job, '_source.status') === 'completed';
if (!isJobSuccessful) {
const errorDoc = await reportingJobQueue.getContent(job._id);
const errorDoc = await jobQueueClient.getContent(job._id);
const text = errorDoc.content;
return toastNotifications.addDanger({
title: `Couldn't create report for ${reportObjectType} '${reportObjectTitle}'`,
@ -112,22 +109,22 @@ uiModules.get('kibana')
return;
}
const jobIds = reportingJobCompletionNotifications.getAll();
const jobIds = jobCompletionNotifications.getAll();
if (!jobIds.length) {
return;
}
const jobs = await getJobs($http, jobIds);
const jobs = await jobQueueClient.list(0, jobIds);
jobIds.forEach(async jobId => {
const job = jobs.find(j => j._id === jobId);
if (!job) {
reportingJobCompletionNotifications.remove(jobId);
jobCompletionNotifications.remove(jobId);
return;
}
if (job._source.status === 'completed' || job._source.status === 'failed') {
await showCompletionNotification(job);
reportingJobCompletionNotifications.remove(job.id);
jobCompletionNotifications.remove(job.id);
return;
}
});
@ -139,21 +136,3 @@ uiModules.get('kibana')
});
poller.start();
});
async function getJobs($http, jobs) {
// Get all jobs in "completed" status since last check, sorted by completion time
const apiBaseUrl = chrome.addBasePath(API_BASE_URL);
// Only getting the first 10, to prevent URL overflows
const url = `${apiBaseUrl}/jobs/list?ids=${jobs.slice(0, 10).join(',')}`;
const headers = addSystemApiHeader({});
const response = await $http.get(url, { headers });
return response.data;
}
function downloadReport(jobId) {
const apiBaseUrl = chrome.addBasePath(API_BASE_URL);
const downloadLink = `${apiBaseUrl}/jobs/download/${jobId}`;
window.open(downloadLink);
}

View file

@ -1,22 +1,3 @@
@import "~ui/styles/variables/colors.less";
.kbn-management-reporting {
.metadata {
color: @kibanaGray3;
}
.error-message {
color: @kibanaRed1;
}
// job list styles
.job-list {
td.actions {
width: 300px;
}
}
.job-list.loading {
opacity: 0.6;
}
.repReportListing__page {
min-height: 100vh;
}

View file

@ -0,0 +1,14 @@
/*
* 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 chrome from 'ui/chrome';
import { API_BASE_URL } from '../../common/constants';
export function downloadReport(jobId: string) {
const apiBaseUrl = chrome.addBasePath(API_BASE_URL);
const downloadLink = `${apiBaseUrl}/jobs/download/${jobId}`;
window.open(downloadLink);
}

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY } from '../../common/constants';
export const jobCompletionNotifications = {
@ -39,7 +38,3 @@ export const jobCompletionNotifications = {
);
},
};
uiModules
.get('xpack/reporting')
.factory('reportingJobCompletionNotifications', () => jobCompletionNotifications);

View file

@ -0,0 +1,55 @@
/*
* 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 { kfetch } from 'ui/kfetch';
// @ts-ignore
import { addSystemApiHeader } from 'ui/system_api';
const API_BASE_URL = '/api/reporting/jobs';
export interface JobQueueEntry {
_id: string;
_source: any;
}
export interface JobContent {
content: string;
content_type: boolean;
}
class JobQueueClient {
public list = (page = 0, jobIds?: string[]): Promise<JobQueueEntry[]> => {
const query = { page } as any;
if (jobIds && jobIds.length > 0) {
// Only getting the first 10, to prevent URL overflows
query.ids = jobIds.slice(0, 10).join(',');
}
return kfetch({
method: 'GET',
pathname: `${API_BASE_URL}/list`,
query,
headers: addSystemApiHeader({}),
});
};
public total(): Promise<number> {
return kfetch({
method: 'GET',
pathname: `${API_BASE_URL}/count`,
headers: addSystemApiHeader({}),
});
}
public getContent(jobId: string): Promise<JobContent> {
return kfetch({
method: 'GET',
pathname: `${API_BASE_URL}/output/${jobId}`,
headers: addSystemApiHeader({}),
});
}
}
export const jobQueueClient = new JobQueueClient();

View file

@ -10,7 +10,7 @@ import { kfetch } from 'ui/kfetch';
import rison from 'rison-node';
import chrome from 'ui/chrome';
import { QueryString } from 'ui/utils/query_string';
import { jobCompletionNotifications } from '../services/job_completion_notifications';
import { jobCompletionNotifications } from './job_completion_notifications';
const API_BASE_URL = '/api/reporting/generate';

View file

@ -1,42 +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 url from 'url';
import { uiModules } from 'ui/modules';
import { addSystemApiHeader } from 'ui/system_api';
const module = uiModules.get('xpack/reporting');
module.service('reportingJobQueue', ($http) => {
const baseUrl = '../api/reporting/jobs';
return {
list(page = 0) {
const urlObj = {
pathname: `${baseUrl}/list`,
query: { page }
};
const headers = addSystemApiHeader({});
return $http.get(url.format(urlObj), { headers })
.then((res) => res.data);
},
total() {
const urlObj = { pathname: `${baseUrl}/count` };
const headers = addSystemApiHeader({});
return $http.get(url.format(urlObj), { headers })
.then((res) => res.data);
},
getContent(jobId) {
const urlObj = { pathname: `${baseUrl}/output/${jobId}` };
return $http.get(url.format(urlObj))
.then((res) => res.data);
}
};
});

View file

@ -1,92 +1,3 @@
<kbn-management-app section="kibana">
<div class="euiPage">
<div class="euiPageBody">
<h1 class="euiTitle">Generated reports</h1>
<table class="table table-striped job-list" ng-class="{ loading: jobsCtrl.loading }">
<thead>
<tr>
<th scope="col">Document</th>
<th scope="col">Added</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-if="!jobsCtrl.reportingJobs.jobs.length">
<td colspan="5">No reports have been created</td>
</tr>
<tr ng-if="jobsCtrl.reportingJobs.jobs.length" ng-repeat="job in jobsCtrl.reportingJobs.jobs">
<td>
<div>{{ job.object_title }}</div>
<div class="metadata">{{ job.object_type }}</div>
</td>
<td>
<div>{{ job.created_at | date : 'yyyy-MM-dd @ h:mm a' }}</div>
<div class="metadata" ng-if="job.created_by">{{ job.created_by }}</div>
</td>
<td>
<div ng-class="{kuiStatusText: true, 'kuiStatusText--warning': job.max_size_reached}">
{{ job.status }}<span ng-if="job.max_size_reached">&nbsp;- max size reached</span>
</div>
<div
class="metadata"
ng-if="job.status === 'processing'"
>
{{ job.started_at | date : 'yyyy-MM-dd @ h:mm a' }}
</div>
<div
class="metadata"
ng-if="job.status === 'completed' || job.status === 'failed'"
>
{{ job.completed_at | date : 'yyyy-MM-dd @ h:mm a' }}
</div>
</td>
<td class="actions">
<button
class="kuiButton kuiButton--danger"
ng-if="job.status === 'failed' && jobsCtrl.errorMessage.job_id !== job.id"
ng-click=jobsCtrl.showError(job.id)
aria-label="Show report-generation error"
>
<span class="kuiIcon fa-question-circle"></span>
</button>
<div
class="error-message"
ng-if="jobsCtrl.errorMessage.job_id === job.id"
>
{{ jobsCtrl.errorMessage.message }}
</div>
<button
ng-if="job.status === 'completed'"
ng-click=jobsCtrl.download(job.id)
ng-class="{ kuiButton: true,
'kuiButton--basic': !job.max_size_reached,
'kuiButton--warning': job.max_size_reached}"
aria-label="Download report"
ng-attr-tooltip="{{
job.max_size_reached ? 'Max size reached, contains partial data.' : null
}}"
>
<span class="kuiIcon fa-download"></span>
</button>
</td>
</tr>
</tbody>
</table>
<div style="text-align: center;">
<paging
page="jobsCtrl.currentPage"
page-size="10"
total="jobsCtrl.reportingJobs.total"
show-prev-next="true"
show-first-last="true"
paging-action="jobsCtrl.setPage(page)">
</paging>
</div>
</div>
</div>
<div id="reportListingAnchor"></div>
</kbn-management-app>

View file

@ -4,140 +4,46 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'angular-paging';
import 'plugins/reporting/services/job_queue';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import 'plugins/reporting/less/main.less';
import { toastNotifications } from 'ui/notify';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import routes from 'ui/routes';
import template from 'plugins/reporting/views/management/jobs.html';
import { Poller } from '../../../../../common/poller';
const pageSize = 10;
import { ReportListing } from '../../components/report_listing';
function mapJobs(jobs) {
return jobs.map((job) => {
return {
id: job._id,
type: job._source.jobtype,
object_type: job._source.payload.type,
object_title: job._source.payload.title,
created_by: job._source.created_by,
created_at: job._source.created_at,
started_at: job._source.started_at,
completed_at: job._source.completed_at,
status: job._source.status,
content_type: job._source.output ? job._source.output.content_type : false,
max_size_reached: job._source.output ? job._source.output.max_size_reached : false
};
});
}
const REACT_ANCHOR_DOM_ELEMENT_ID = 'reportListingAnchor';
routes.when('/management/kibana/reporting', {
template,
controllerAs: 'jobsCtrl',
controller($scope, $route, $window, $interval, reportingJobQueue, kbnUrl, Private, reportingPollConfig) {
const { jobsRefresh } = reportingPollConfig;
controller($scope, kbnUrl, Private) {
const xpackInfo = Private(XPackInfoProvider);
this.loading = false;
this.pageSize = pageSize;
this.currentPage = 1;
this.reportingJobs = [];
$scope.$$postDigest(() => {
const node = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
if (!node) {
return;
}
const licenseAllowsToShowThisPage = () => {
return xpackInfo.get('features.reporting.management.showLinks')
&& xpackInfo.get('features.reporting.management.enableLinks');
};
const notifyAndRedirectToManagementOverviewPage = () => {
toastNotifications.addDanger(xpackInfo.get('features.reporting.management.message'));
kbnUrl.redirect('/management');
return Promise.reject();
};
const getJobs = (page = 0) => {
return reportingJobQueue.list(page)
.then((jobs) => {
return reportingJobQueue.total()
.then((total) => {
const mappedJobs = mapJobs(jobs);
return {
jobs: mappedJobs,
total: total,
pages: Math.ceil(total / pageSize),
};
});
})
.catch((err) => {
if (!licenseAllowsToShowThisPage()) {
return notifyAndRedirectToManagementOverviewPage();
}
if (err.status !== 401 && err.status !== 403) {
toastNotifications.addDanger(err.statusText || 'Request failed');
}
return {
jobs: [],
total: 0,
pages: 1,
};
});
};
const toggleLoading = () => {
this.loading = !this.loading;
};
const updateJobs = () => {
return getJobs(this.currentPage - 1)
.then((jobs) => {
this.reportingJobs = jobs;
});
};
const updateJobsLoading = () => {
toggleLoading();
updateJobs().then(toggleLoading);
};
// pagination logic
this.setPage = (page) => {
this.currentPage = page;
};
// job list updating
const poller = new Poller({
functionToPoll: () => {
return updateJobs();
},
pollFrequencyInMillis: jobsRefresh.interval,
trailing: true,
continuePollingOnError: true,
pollFrequencyErrorMultiplier: jobsRefresh.intervalErrorMultiplier
render(
<ReportListing
badLicenseMessage={xpackInfo.get('features.reporting.management.message')}
showLinks={xpackInfo.get('features.reporting.management.showLinks')}
enableLinks={xpackInfo.get('features.reporting.management.enableLinks')}
redirect={kbnUrl.redirect}
/>,
node,
);
});
poller.start();
// control handlers
this.download = (jobId) => {
$window.open(`../api/reporting/jobs/download/${jobId}`);
};
// fetch and show job error details
this.showError = (jobId) => {
reportingJobQueue.getContent(jobId)
.then((doc) => {
this.errorMessage = {
job_id: jobId,
message: doc.content,
};
});
};
$scope.$watch('jobsCtrl.currentPage', updateJobsLoading);
$scope.$on('$destroy', () => poller.stop());
$scope.$on('$destroy', () => {
const node = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
if (node) {
unmountComponentAtNode(node);
}
});
}
});

View file

@ -345,10 +345,6 @@ ammo@2.x.x:
boom "5.x.x"
hoek "4.x.x"
angular-paging@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/angular-paging/-/angular-paging-2.2.1.tgz#8090864f71bc4c9b89307b02ab02afb205983c43"
angular-resource@1.4.9:
version "1.4.9"
resolved "https://registry.yarnpkg.com/angular-resource/-/angular-resource-1.4.9.tgz#67f09382b623fd7e61540b0d127dba99fda99d45"

View file

@ -853,10 +853,6 @@ angular-mocks@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.4.7.tgz#d7343ee0a033f9216770bda573950f6814d95227"
angular-paging@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/angular-paging/-/angular-paging-2.2.1.tgz#8090864f71bc4c9b89307b02ab02afb205983c43"
angular-recursion@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/angular-recursion/-/angular-recursion-1.0.5.tgz#cd405428a0bf55faf52eaa7988c1fe69cd930543"