From a442ab986341395350d5fafa09a641fe3fcdf832 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 21 Nov 2023 09:51:10 -0700 Subject: [PATCH 1/3] Remove unused interface --- packages/kbn-reporting/common/url.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/kbn-reporting/common/url.ts b/packages/kbn-reporting/common/url.ts index b7f9587768d18..14772b3a612aa 100644 --- a/packages/kbn-reporting/common/url.ts +++ b/packages/kbn-reporting/common/url.ts @@ -35,10 +35,6 @@ export interface IlmPolicyStatusResponse { status: IlmPolicyMigrationStatus; } -export interface DefaultPolicyStatusResponse { - status: undefined; -} - type Url = string; type UrlLocatorTuple = [url: Url, locatorParams: LocatorParams]; From 1a6bb9fc94240587fd2a42ed2c2079a14b8072fc Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 21 Nov 2023 09:52:05 -0700 Subject: [PATCH 2/3] simplify mount_management_section, delegate to logic in PolicyStatusProvider --- .../management/mount_management_section.tsx | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/reporting/public/management/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx index fe0df4e64eeef..575d9e7c8d7cc 100644 --- a/x-pack/plugins/reporting/public/management/mount_management_section.tsx +++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx @@ -15,7 +15,6 @@ import { ILicense } from '@kbn/licensing-plugin/public'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import type { ClientConfigType } from '@kbn/reporting-public'; import { ReportListing } from '.'; -import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context'; import { InternalApiClientProvider, ReportingAPIClient } from '../lib/reporting_api_client'; import type { ManagementAppMountParams, SharePluginSetup } from '../shared_imports'; import { KibanaContextProvider } from '../shared_imports'; @@ -30,29 +29,6 @@ export async function mountManagementSection( urlService: SharePluginSetup['url'], params: ManagementAppMountParams ) { - const CtxProvider = config.statefulSettings.enabled ? ( - - - - ) : ( - - - - ); render( @@ -64,7 +40,19 @@ export async function mountManagementSection( docLinks: coreStart.docLinks, }} > - {CtxProvider} + + + + + , From 9c4b7cc83afa48a474e7bd433ea721f0f9bc1a7b Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 21 Nov 2023 09:52:49 -0700 Subject: [PATCH 3/3] Break up report_listing.tsx into Default, Stateful, and ListingTable --- .../default/report_listing_default.tsx | 50 ++ .../reporting/public/management/index.ts | 7 +- .../public/management/report_listing.tsx | 573 +----------------- .../management/report_listing_table.tsx | 440 ++++++++++++++ .../stateful/report_listing_stateful.tsx | 84 +++ 5 files changed, 583 insertions(+), 571 deletions(-) create mode 100644 x-pack/plugins/reporting/public/management/default/report_listing_default.tsx create mode 100644 x-pack/plugins/reporting/public/management/report_listing_table.tsx create mode 100644 x-pack/plugins/reporting/public/management/stateful/report_listing_stateful.tsx diff --git a/x-pack/plugins/reporting/public/management/default/report_listing_default.tsx b/x-pack/plugins/reporting/public/management/default/report_listing_default.tsx new file mode 100644 index 0000000000000..a2b09ccd3c177 --- /dev/null +++ b/x-pack/plugins/reporting/public/management/default/report_listing_default.tsx @@ -0,0 +1,50 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { ListingPropsInternal } from '..'; +import { ReportListingTable } from '../report_listing_table'; + +/** + * Used in non-stateful (Serverless) + * Does not render controls for features only applicable in Stateful + */ +export const ReportListingDefault: FC = (props) => { + const { apiClient, capabilities, config, navigateToUrl, toasts, urlService, ...listingProps } = + props; + return ( + <> + + } + description={ + + } + /> + + + + ); +}; diff --git a/x-pack/plugins/reporting/public/management/index.ts b/x-pack/plugins/reporting/public/management/index.ts index 7d55e18d32cc0..bdd6d0a9916b7 100644 --- a/x-pack/plugins/reporting/public/management/index.ts +++ b/x-pack/plugins/reporting/public/management/index.ts @@ -8,20 +8,21 @@ import type { ApplicationStart, ToastsSetup } from '@kbn/core/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { ClientConfigType } from '@kbn/reporting-public'; -import type { UseIlmPolicyStatusReturn } from '../lib/ilm_policy_status_context'; import type { ReportingAPIClient } from '../lib/reporting_api_client'; import type { SharePluginSetup } from '../shared_imports'; export interface ListingProps { apiClient: ReportingAPIClient; - capabilities: ApplicationStart['capabilities']; license$: LicensingPluginStart['license$']; config: ClientConfigType; redirect: ApplicationStart['navigateToApp']; navigateToUrl: ApplicationStart['navigateToUrl']; toasts: ToastsSetup; urlService: SharePluginSetup['url']; - ilmPolicyContextValue?: UseIlmPolicyStatusReturn; } +export type ListingPropsInternal = ListingProps & { + capabilities: ApplicationStart['capabilities']; +}; + export { ReportListing } from './report_listing'; diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index 04dce76efdb0d..024d75b04ae10 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -5,580 +5,17 @@ * 2.0. */ -import { Component, Fragment, default as React, FC } from 'react'; -import { Subscription } from 'rxjs'; +import React from 'react'; -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiFlexGroup, - EuiFlexItem, - EuiIconTip, - EuiLink, - EuiLoadingSpinner, - EuiPageHeader, - EuiSpacer, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { ILicense } from '@kbn/licensing-plugin/public'; -import { durationToNumber } from '@kbn/reporting-common'; - -import { ApplicationStart } from '@kbn/core-application-browser'; import { ListingProps as Props } from '.'; -import { REPORT_TABLE_ID, REPORT_TABLE_ROW_ID } from '../../common/constants'; -import { prettyPrintJobType } from '../../common/job_utils'; -import { Poller } from '../../common/poller'; -import { useIlmPolicyStatus } from '../lib/ilm_policy_status_context'; -import { Job } from '../lib/job'; -import { checkLicense } from '../lib/license_check'; -import { ReportingAPIClient, useInternalApiClient } from '../lib/reporting_api_client'; +import { useInternalApiClient } from '../lib/reporting_api_client'; import { useKibana } from '../shared_imports'; -import { - IlmPolicyLink, - MigrateIlmPolicyCallOut, - ReportDeleteButton, - ReportDiagnostic, - ReportInfoFlyout, - ReportStatusIndicator, -} from './components'; -import { guessAppIconTypeFromObjectType } from './utils'; import './report_listing.scss'; +import { ReportListingStateful } from './stateful/report_listing_stateful'; +import { ReportListingDefault } from './default/report_listing_default'; -type TableColumn = EuiBasicTableColumn; - -interface State { - page: number; - total: number; - jobs: Job[]; - selectedJobs: Job[]; - isLoading: boolean; - showLinks: boolean; - enableLinks: boolean; - badLicenseMessage: string; - selectedJob: undefined | Job; -} - -class ReportListingUi extends Component { - private isInitialJobsFetch: boolean; - private licenseSubscription?: Subscription; - private mounted?: boolean; - private poller?: Poller; - - constructor(props: Props) { - super(props); - - this.state = { - page: 0, - total: 0, - jobs: [], - selectedJobs: [], - isLoading: false, - showLinks: false, - enableLinks: false, - badLicenseMessage: '', - selectedJob: undefined, - }; - - this.isInitialJobsFetch = true; - } - - public render() { - const { - apiClient, - toasts, - ilmPolicyContextValue, - urlService, - navigateToUrl, - capabilities, - config, - } = this.props; - if (config.statefulSettings.enabled) { - const ilmLocator = urlService.locators.get('ILM_LOCATOR_ID'); - const hasIlmPolicy = ilmPolicyContextValue?.status !== 'policy-not-found'; - const showIlmPolicyLink = Boolean(ilmLocator && hasIlmPolicy); - return ( - <> - - } - description={ - - } - /> - - - - -
{this.renderTable()}
- - - - {capabilities?.management?.data?.index_lifecycle_management && ( - - {ilmPolicyContextValue?.isLoading ? ( - - ) : ( - showIlmPolicyLink && ( - - ) - )} - - )} - - - - - - ); - } else { - return ( - <> - - } - description={ - - } - /> - - -
{this.renderTable()}
- - - - - - - - - ); - } - } - - public componentWillUnmount() { - this.mounted = false; - this.poller?.stop(); - - if (this.licenseSubscription) { - this.licenseSubscription.unsubscribe(); - } - } - - public componentDidMount() { - this.mounted = true; - const { config, license$ } = this.props; - const pollFrequencyInMillis = durationToNumber(config.poll.jobsRefresh.interval); - this.poller = new Poller({ - functionToPoll: () => { - return this.fetchJobs(); - }, - pollFrequencyInMillis, - trailing: false, - continuePollingOnError: true, - pollFrequencyErrorMultiplier: config.poll.jobsRefresh.intervalErrorMultiplier, - }); - this.poller.start(); - this.licenseSubscription = license$.subscribe(this.licenseHandler); - } - - private licenseHandler = (license: ILicense) => { - const { - enableLinks, - showLinks, - message: badLicenseMessage, - } = checkLicense(license.check('reporting', 'basic')); - - this.setState({ - enableLinks, - showLinks, - badLicenseMessage, - }); - }; - - private onSelectionChange = (jobs: Job[]) => { - this.setState((current) => ({ ...current, selectedJobs: jobs })); - }; - - private removeJob = (job: Job) => { - const { jobs } = this.state; - const filtered = jobs.filter((j) => j.id !== job.id); - this.setState((current) => ({ ...current, jobs: filtered })); - }; - - private renderDeleteButton = () => { - const { selectedJobs } = this.state; - if (selectedJobs.length === 0) return undefined; - - const performDelete = async () => { - for (const job of selectedJobs) { - try { - await this.props.apiClient.deleteReport(job.id); - this.removeJob(job); - this.props.toasts.addSuccess( - i18n.translate('xpack.reporting.listing.table.deleteConfim', { - defaultMessage: `The {reportTitle} report was deleted`, - values: { - reportTitle: job.title, - }, - }) - ); - } catch (error) { - this.props.toasts.addDanger( - i18n.translate('xpack.reporting.listing.table.deleteFailedErrorMessage', { - defaultMessage: `The report was not deleted: {error}`, - values: { error }, - }) - ); - throw error; - } - } - }; - - return ( - - ); - }; - - 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: Job[]; - let total: number; - try { - jobs = await this.props.apiClient.list(this.state.page); - total = await this.props.apiClient.total(); - this.isInitialJobsFetch = false; - } catch (fetchError) { - if (!this.licenseAllowsToShowThisPage()) { - this.props.toasts.addDanger(this.state.badLicenseMessage); - this.props.redirect('management'); - return; - } - - if (fetchError.message === 'Failed to fetch') { - this.props.toasts.addDanger( - fetchError.message || - i18n.translate('xpack.reporting.listing.table.requestFailedErrorMessage', { - defaultMessage: 'Request failed', - }) - ); - } - if (this.mounted) { - this.setState(() => ({ isLoading: false, jobs: [], total: 0 })); - } - return; - } - - if (this.mounted) { - this.setState(() => ({ - isLoading: false, - total, - jobs, - })); - } - }; - - private licenseAllowsToShowThisPage = () => { - return this.state.showLinks && this.state.enableLinks; - }; - - /** - * Widths like this are not the best, but the auto-layout does not play well with text in links. We can update - * this with something that works better on all screen sizes. This works for desktop, mobile fallback is provided on a - * per column basis. - */ - private readonly tableColumnWidths = { - type: '5%', - title: '30%', - status: '20%', - createdAt: '25%', - content: '10%', - actions: '10%', - }; - - private renderTable() { - const { tableColumnWidths } = this; - const tableColumns: TableColumn[] = [ - { - field: 'type', - width: tableColumnWidths.type, - name: i18n.translate('xpack.reporting.listing.tableColumns.typeTitle', { - defaultMessage: 'Type', - }), - render: (_type: string, job) => { - return ( -
- -
- ); - }, - mobileOptions: { - show: true, - render: (job) => { - return
{job.objectType}
; - }, - }, - }, - { - field: 'title', - name: i18n.translate('xpack.reporting.listing.tableColumns.reportTitle', { - defaultMessage: 'Title', - }), - width: tableColumnWidths.title, - render: (objectTitle: string, job) => { - return ( -
- this.setState({ selectedJob: job })} - > - {objectTitle || - i18n.translate('xpack.reporting.listing.table.noTitleLabel', { - defaultMessage: 'Untitled', - })} - -
- ); - }, - mobileOptions: { - header: false, - width: '100%', // This is not recognized by EUI types but has an effect, leaving for now - } as unknown as { header: boolean }, - }, - { - field: 'status', - width: tableColumnWidths.status, - name: i18n.translate('xpack.reporting.listing.tableColumns.statusTitle', { - defaultMessage: 'Status', - }), - render: (_status: string, job) => { - return ( - - - - ); - }, - mobileOptions: { - show: false, - }, - }, - { - field: 'created_at', - width: tableColumnWidths.createdAt, - name: i18n.translate('xpack.reporting.listing.tableColumns.createdAtTitle', { - defaultMessage: 'Created at', - }), - render: (_createdAt: string, job) => ( -
{job.getCreatedAtDate()}
- ), - mobileOptions: { - show: false, - }, - }, - { - field: 'content', - width: tableColumnWidths.content, - name: i18n.translate('xpack.reporting.listing.tableColumns.content', { - defaultMessage: 'Content', - }), - render: (_status: string, job) => prettyPrintJobType(job.jobtype), - mobileOptions: { - show: false, - }, - }, - { - name: i18n.translate('xpack.reporting.listing.tableColumns.actionsTitle', { - defaultMessage: 'Actions', - }), - width: tableColumnWidths.actions, - actions: [ - { - isPrimary: true, - 'data-test-subj': 'reportDownloadLink', - type: 'icon', - icon: 'download', - name: i18n.translate('xpack.reporting.listing.table.downloadReportButtonLabel', { - defaultMessage: 'Download report', - }), - description: i18n.translate('xpack.reporting.listing.table.downloadReportDescription', { - defaultMessage: 'Download this report in a new tab.', - }), - onClick: (job) => this.props.apiClient.downloadReport(job.id), - enabled: (job) => job.isDownloadReady, - }, - { - name: i18n.translate( - 'xpack.reporting.listing.table.viewReportingInfoActionButtonLabel', - { - defaultMessage: 'View report info', - } - ), - description: i18n.translate( - 'xpack.reporting.listing.table.viewReportingInfoActionButtonDescription', - { - defaultMessage: 'View additional information about this report.', - } - ), - type: 'icon', - icon: 'iInCircle', - onClick: (job) => this.setState({ selectedJob: job }), - }, - { - name: i18n.translate('xpack.reporting.listing.table.openInKibanaAppLabel', { - defaultMessage: 'Open in Kibana', - }), - 'data-test-subj': 'reportOpenInKibanaApp', - description: i18n.translate( - 'xpack.reporting.listing.table.openInKibanaAppDescription', - { - defaultMessage: 'Open the Kibana app where this report was generated.', - } - ), - available: (job) => job.canLinkToKibanaApp, - type: 'icon', - icon: 'popout', - onClick: (job) => { - const href = this.props.apiClient.getKibanaAppHref(job); - window.open(href, '_blank'); - window.focus(); - }, - }, - ], - }, - ]; - - const pagination = { - pageIndex: this.state.page, - pageSize: 10, - totalItemCount: this.state.total, - showPerPageOptions: false, - }; - - const selection = { - itemId: 'id', - onSelectionChange: this.onSelectionChange, - }; - - return ( - - {this.state.selectedJobs.length > 0 && ( - - - {this.renderDeleteButton()} - - - - )} - ({ 'data-test-subj': REPORT_TABLE_ROW_ID })} - /> - {!!this.state.selectedJob && ( - this.setState({ selectedJob: undefined })} - job={this.state.selectedJob} - /> - )} - - ); - } -} - -type ReportListingProps = Omit< - Props, - 'ilmPolicyContextValue' | 'intl' | 'apiClient' | 'capabilities' | 'configAllowsImages' ->; - -const ReportListingStateful: FC< - ReportListingProps & { - apiClient: ReportingAPIClient; - capabilities: ApplicationStart['capabilities']; - } -> = (props) => { - const { apiClient, capabilities, ...listingProps } = props; - return ( - - ); -}; - -const ReportListingDefault: FC< - ReportListingProps & { - apiClient: ReportingAPIClient; - capabilities: ApplicationStart['capabilities']; - } -> = (props) => { - const { apiClient, capabilities, ...listingProps } = props; - return ; -}; - -export const ReportListing = (props: ReportListingProps) => { +export const ReportListing = (props: Props) => { const { apiClient } = useInternalApiClient(); const { services: { diff --git a/x-pack/plugins/reporting/public/management/report_listing_table.tsx b/x-pack/plugins/reporting/public/management/report_listing_table.tsx new file mode 100644 index 0000000000000..61b9e7d541928 --- /dev/null +++ b/x-pack/plugins/reporting/public/management/report_listing_table.tsx @@ -0,0 +1,440 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Component, Fragment, default as React } from 'react'; +import { Subscription } from 'rxjs'; + +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ILicense } from '@kbn/licensing-plugin/public'; +import { durationToNumber } from '@kbn/reporting-common'; + +import { REPORT_TABLE_ID, REPORT_TABLE_ROW_ID } from '../../common/constants'; +import { prettyPrintJobType } from '../../common/job_utils'; +import { Poller } from '../../common/poller'; +import { Job } from '../lib/job'; +import { checkLicense } from '../lib/license_check'; +import { ReportDeleteButton, ReportInfoFlyout, ReportStatusIndicator } from './components'; +import { guessAppIconTypeFromObjectType } from './utils'; +import { ListingPropsInternal } from '.'; + +type TableColumn = EuiBasicTableColumn; + +interface State { + page: number; + total: number; + jobs: Job[]; + selectedJobs: Job[]; + isLoading: boolean; + showLinks: boolean; + enableLinks: boolean; + badLicenseMessage: string; + selectedJob: undefined | Job; +} + +export class ReportListingTable extends Component { + private isInitialJobsFetch: boolean; + private licenseSubscription?: Subscription; + private mounted?: boolean; + private poller?: Poller; + + constructor(props: ListingPropsInternal) { + super(props); + + this.state = { + page: 0, + total: 0, + jobs: [], + selectedJobs: [], + isLoading: false, + showLinks: false, + enableLinks: false, + badLicenseMessage: '', + selectedJob: undefined, + }; + + this.isInitialJobsFetch = true; + } + + public componentWillUnmount() { + this.mounted = false; + this.poller?.stop(); + + if (this.licenseSubscription) { + this.licenseSubscription.unsubscribe(); + } + } + + public componentDidMount() { + this.mounted = true; + const { config, license$ } = this.props; + const pollFrequencyInMillis = durationToNumber(config.poll.jobsRefresh.interval); + this.poller = new Poller({ + functionToPoll: () => { + return this.fetchJobs(); + }, + pollFrequencyInMillis, + trailing: false, + continuePollingOnError: true, + pollFrequencyErrorMultiplier: config.poll.jobsRefresh.intervalErrorMultiplier, + }); + this.poller.start(); + this.licenseSubscription = license$.subscribe(this.licenseHandler); + } + + private licenseHandler = (license: ILicense) => { + const { + enableLinks, + showLinks, + message: badLicenseMessage, + } = checkLicense(license.check('reporting', 'basic')); + + this.setState({ + enableLinks, + showLinks, + badLicenseMessage, + }); + }; + + private onSelectionChange = (jobs: Job[]) => { + this.setState((current) => ({ ...current, selectedJobs: jobs })); + }; + + private removeJob = (job: Job) => { + const { jobs } = this.state; + const filtered = jobs.filter((j) => j.id !== job.id); + this.setState((current) => ({ ...current, jobs: filtered })); + }; + + private renderDeleteButton = () => { + const { selectedJobs } = this.state; + if (selectedJobs.length === 0) return undefined; + + const performDelete = async () => { + for (const job of selectedJobs) { + try { + await this.props.apiClient.deleteReport(job.id); + this.removeJob(job); + this.props.toasts.addSuccess( + i18n.translate('xpack.reporting.listing.table.deleteConfim', { + defaultMessage: `The {reportTitle} report was deleted`, + values: { + reportTitle: job.title, + }, + }) + ); + } catch (error) { + this.props.toasts.addDanger( + i18n.translate('xpack.reporting.listing.table.deleteFailedErrorMessage', { + defaultMessage: `The report was not deleted: {error}`, + values: { error }, + }) + ); + throw error; + } + } + }; + + return ( + + ); + }; + + 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: Job[]; + let total: number; + try { + jobs = await this.props.apiClient.list(this.state.page); + total = await this.props.apiClient.total(); + this.isInitialJobsFetch = false; + } catch (fetchError) { + if (!this.licenseAllowsToShowThisPage()) { + this.props.toasts.addDanger(this.state.badLicenseMessage); + this.props.redirect('management'); + return; + } + + if (fetchError.message === 'Failed to fetch') { + this.props.toasts.addDanger( + fetchError.message || + i18n.translate('xpack.reporting.listing.table.requestFailedErrorMessage', { + defaultMessage: 'Request failed', + }) + ); + } + if (this.mounted) { + this.setState(() => ({ isLoading: false, jobs: [], total: 0 })); + } + return; + } + + if (this.mounted) { + this.setState(() => ({ + isLoading: false, + total, + jobs, + })); + } + }; + + private licenseAllowsToShowThisPage = () => { + return this.state.showLinks && this.state.enableLinks; + }; + + /** + * Widths like this are not the best, but the auto-layout does not play well with text in links. We can update + * this with something that works better on all screen sizes. This works for desktop, mobile fallback is provided on a + * per column basis. + */ + private readonly tableColumnWidths = { + type: '5%', + title: '30%', + status: '20%', + createdAt: '25%', + content: '10%', + actions: '10%', + }; + + public render() { + const { tableColumnWidths } = this; + const tableColumns: TableColumn[] = [ + { + field: 'type', + width: tableColumnWidths.type, + name: i18n.translate('xpack.reporting.listing.tableColumns.typeTitle', { + defaultMessage: 'Type', + }), + render: (_type: string, job) => { + return ( +
+ +
+ ); + }, + mobileOptions: { + show: true, + render: (job) => { + return
{job.objectType}
; + }, + }, + }, + { + field: 'title', + name: i18n.translate('xpack.reporting.listing.tableColumns.reportTitle', { + defaultMessage: 'Title', + }), + width: tableColumnWidths.title, + render: (objectTitle: string, job) => { + return ( +
+ this.setState({ selectedJob: job })} + > + {objectTitle || + i18n.translate('xpack.reporting.listing.table.noTitleLabel', { + defaultMessage: 'Untitled', + })} + +
+ ); + }, + mobileOptions: { + header: false, + width: '100%', // This is not recognized by EUI types but has an effect, leaving for now + } as unknown as { header: boolean }, + }, + { + field: 'status', + width: tableColumnWidths.status, + name: i18n.translate('xpack.reporting.listing.tableColumns.statusTitle', { + defaultMessage: 'Status', + }), + render: (_status: string, job) => { + return ( + + + + ); + }, + mobileOptions: { + show: false, + }, + }, + { + field: 'created_at', + width: tableColumnWidths.createdAt, + name: i18n.translate('xpack.reporting.listing.tableColumns.createdAtTitle', { + defaultMessage: 'Created at', + }), + render: (_createdAt: string, job) => ( +
{job.getCreatedAtDate()}
+ ), + mobileOptions: { + show: false, + }, + }, + { + field: 'content', + width: tableColumnWidths.content, + name: i18n.translate('xpack.reporting.listing.tableColumns.content', { + defaultMessage: 'Content', + }), + render: (_status: string, job) => prettyPrintJobType(job.jobtype), + mobileOptions: { + show: false, + }, + }, + { + name: i18n.translate('xpack.reporting.listing.tableColumns.actionsTitle', { + defaultMessage: 'Actions', + }), + width: tableColumnWidths.actions, + actions: [ + { + isPrimary: true, + 'data-test-subj': 'reportDownloadLink', + type: 'icon', + icon: 'download', + name: i18n.translate('xpack.reporting.listing.table.downloadReportButtonLabel', { + defaultMessage: 'Download report', + }), + description: i18n.translate('xpack.reporting.listing.table.downloadReportDescription', { + defaultMessage: 'Download this report in a new tab.', + }), + onClick: (job) => this.props.apiClient.downloadReport(job.id), + enabled: (job) => job.isDownloadReady, + }, + { + name: i18n.translate( + 'xpack.reporting.listing.table.viewReportingInfoActionButtonLabel', + { + defaultMessage: 'View report info', + } + ), + description: i18n.translate( + 'xpack.reporting.listing.table.viewReportingInfoActionButtonDescription', + { + defaultMessage: 'View additional information about this report.', + } + ), + type: 'icon', + icon: 'iInCircle', + onClick: (job) => this.setState({ selectedJob: job }), + }, + { + name: i18n.translate('xpack.reporting.listing.table.openInKibanaAppLabel', { + defaultMessage: 'Open in Kibana', + }), + 'data-test-subj': 'reportOpenInKibanaApp', + description: i18n.translate( + 'xpack.reporting.listing.table.openInKibanaAppDescription', + { + defaultMessage: 'Open the Kibana app where this report was generated.', + } + ), + available: (job) => job.canLinkToKibanaApp, + type: 'icon', + icon: 'popout', + onClick: (job) => { + const href = this.props.apiClient.getKibanaAppHref(job); + window.open(href, '_blank'); + window.focus(); + }, + }, + ], + }, + ]; + + const pagination = { + pageIndex: this.state.page, + pageSize: 10, + totalItemCount: this.state.total, + showPerPageOptions: false, + }; + + const selection = { + itemId: 'id', + onSelectionChange: this.onSelectionChange, + }; + + return ( + + {this.state.selectedJobs.length > 0 && ( + + + {this.renderDeleteButton()} + + + + )} + ({ 'data-test-subj': REPORT_TABLE_ROW_ID })} + /> + {!!this.state.selectedJob && ( + this.setState({ selectedJob: undefined })} + job={this.state.selectedJob} + /> + )} + + ); + } +} diff --git a/x-pack/plugins/reporting/public/management/stateful/report_listing_stateful.tsx b/x-pack/plugins/reporting/public/management/stateful/report_listing_stateful.tsx new file mode 100644 index 0000000000000..4ddb569b1f355 --- /dev/null +++ b/x-pack/plugins/reporting/public/management/stateful/report_listing_stateful.tsx @@ -0,0 +1,84 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPageHeader, + EuiSpacer, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { ListingPropsInternal } from '..'; +import { useIlmPolicyStatus } from '../../lib/ilm_policy_status_context'; +import { IlmPolicyLink, MigrateIlmPolicyCallOut, ReportDiagnostic } from '../components'; +import { ReportListingTable } from '../report_listing_table'; + +/** + * Used in Stateful deployments only + * Renders controls for ILM and Screenshotting Diagnostics which are only applicable in Stateful + */ +export const ReportListingStateful: FC = (props) => { + const { apiClient, capabilities, config, navigateToUrl, toasts, urlService, ...listingProps } = + props; + const ilmLocator = urlService.locators.get('ILM_LOCATOR_ID'); + const ilmPolicyContextValue = useIlmPolicyStatus(); + const hasIlmPolicy = ilmPolicyContextValue?.status !== 'policy-not-found'; + const showIlmPolicyLink = Boolean(ilmLocator && hasIlmPolicy); + return ( + <> + + } + description={ + + } + /> + + + + + + + + + + {capabilities?.management?.data?.index_lifecycle_management && ( + + {ilmPolicyContextValue?.isLoading ? ( + + ) : ( + showIlmPolicyLink && ( + + ) + )} + + )} + + + + + + ); +};