diff --git a/kibana-reports/public/components/main/main.tsx b/kibana-reports/public/components/main/main.tsx index 7bb0967f..e936314c 100644 --- a/kibana-reports/public/components/main/main.tsx +++ b/kibana-reports/public/components/main/main.tsx @@ -23,6 +23,7 @@ import { EuiHorizontalRule, EuiSpacer, EuiPanel, + EuiGlobalToastList, } from '@elastic/eui'; import { ReportsTable } from './reports_table'; import { ReportDefinitions } from './report_definitions_table'; @@ -30,119 +31,149 @@ import { addReportsTableContent, addReportDefinitionsTableContent, } from './main_utils'; -import { CoreInterface } from '../app'; import CSS from 'csstype'; -interface RouterHomeProps extends CoreInterface { - httpClient?: any; - reportDefinitionCreateSuccess?: boolean -} - const reportCountStyles: CSS.Properties = { color: 'gray', display: 'inline', }; -export class Main extends React.Component { - constructor(props: any) { - super(props); - this.state = { - pagination: this.pagination, - generateReportFilename: '', - generateReportFileFormat: '', - generateReportStatus: '', - reportsTableContent: [], - reportDefinitionsTableContent: [], - reportSourceDashboardOptions: [], +export function Main(props) { + const [reportsTableContent, setReportsTableContent] = useState([]); + const [ + reportDefinitionsTableContent, + setReportDefinitionsTableContent, + ] = useState([]); + const [toasts, setToasts] = useState([]); + + const addReportsTableContentErrorToastHandler = () => { + const errorToast = { + title: 'Error generating reports table content', + color: 'danger', + iconType: 'alert', + id: 'reportsTableErrorToast', }; - } + setToasts(toasts.concat(errorToast)); + }; - pagination = { - initialPageSize: 10, - pageSizeOptions: [8, 10, 13], + const handleReportsTableContentErrorToast = () => { + addReportsTableContentErrorToastHandler(); }; - updateReportsTableContent = async () => { - const { httpClient } = this.props; - httpClient - .get('../api/reporting/reports') - .then((response) => { - this.setState({ - reportsTableContent: addReportsTableContent(response.data), - }); - }) - .catch((error) => { - console.log('error when fetching all reports: ', error); - }); + const addReportDefinitionsTableErrorToastHandler = () => { + const errorToast = { + title: 'Error generating report definitions table content', + color: 'danger', + iconType: 'alert', + id: 'reportDefinitionsTableErrorToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleReportDefinitionsTableErrorToast = () => { + addReportDefinitionsTableErrorToastHandler(); + }; + + const addErrorOnDemandDownloadToastHandler = () => { + const errorToast = { + title: 'Error downloading report', + color: 'danger', + iconType: 'alert', + id: 'onDemandDownloadErrorToast', + }; + setToasts(toasts.concat(errorToast)); }; - componentDidMount = async () => { - this.props.setBreadcrumbs([ + const handleOnDemandDownloadErrorToast = () => { + addErrorOnDemandDownloadToastHandler(); + }; + + const addSuccessOnDemandDownloadToastHandler = () => { + const successToast = { + title: 'Success', + color: 'success', + text:

Report successfully downloaded!

, + id: 'onDemandDownloadSuccessToast', + }; + setToasts(toasts.concat(successToast)); + }; + + const handleOnDemandDownloadSuccessToast = () => { + addSuccessOnDemandDownloadToastHandler(); + }; + + const removeToast = (removedToast) => { + setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); + }; + + const pagination = { + initialPageSize: 10, + pageSizeOptions: [8, 10, 13], + }; + + useEffect(() => { + props.setBreadcrumbs([ { text: 'Reporting', href: '#', }, ]); - const { httpClient } = this.props; + const { httpClient } = props; // get all reports - await httpClient + httpClient .get('../api/reporting/reports') .then((response) => { - this.setState({ - reportsTableContent: addReportsTableContent(response.data), - }); + setReportsTableContent(addReportsTableContent(response.data)); }) .catch((error) => { console.log('error when fetching all reports: ', error); + handleReportsTableContentErrorToast(); }); // get all report definitions - await httpClient + httpClient .get('../api/reporting/reportDefinitions') .then((response) => { - this.setState({ - reportDefinitionsTableContent: addReportDefinitionsTableContent( - response.data - ), - }); + setReportDefinitionsTableContent( + addReportDefinitionsTableContent(response.data) + ); }) .catch((error) => { console.log('error when fetching all report definitions: ', error); + handleReportDefinitionsTableErrorToast(); }); - }; + }, []); - refreshReportsTable = async () => { - const { httpClient } = this.props; + const refreshReportsTable = async () => { + const { httpClient } = props; await httpClient .get('../api/reporting/reports') .then((response) => { - this.setState({ - reportsTableContent: addReportsTableContent(response.data), - }); + setReportsTableContent(addReportsTableContent(response.data)); addReportsTableContent(response.data); }) .catch((error) => { console.log('error when fetching all reports: ', error); + handleReportsTableContentErrorToast(); }); }; - refreshReportsDefinitionsTable = async () => { - const { httpClient } = this.props; + const refreshReportsDefinitionsTable = async () => { + const { httpClient } = props; await httpClient .get('../api/reporting/reportDefinitions') .then((response) => { - this.setState({ - reportDefinitionsTableContent: addReportDefinitionsTableContent( - response.data - ), - }); + setReportDefinitionsTableContent( + addReportDefinitionsTableContent(response.data) + ); }) .catch((error) => { console.log('error when fetching all report definitions: ', error); + handleReportDefinitionsTableErrorToast(); }); }; - getReportsRowProps = (item: any) => { + const getReportsRowProps = (item: any) => { const { id } = item; return { 'data-test-subj': `row-${id}`, @@ -150,14 +181,14 @@ export class Main extends React.Component { onClick: (e: any) => { if (!$(e.target).is('button')) { window.location.assign( - `opendistro_kibana_reports#/report_details/${item.id}${this.props.history.location.search}` + `opendistro_kibana_reports#/report_details/${item.id}${props.history.location.search}` ); } }, }; }; - getReportDefinitionsRowProps = (item: any) => { + const getReportDefinitionsRowProps = (item: any) => { const { id } = item; return { 'data-test-subj': `row-${id}`, @@ -165,83 +196,83 @@ export class Main extends React.Component { onClick: (e: any) => { if (!$(e.target).is('button')) { window.location.assign( - `opendistro_kibana_reports#/report_definition_details/${item.id}${this.props.history.location.search}` + `opendistro_kibana_reports#/report_definition_details/${item.id}${props.history.location.search}` ); } }, }; }; - render() { - return ( -
- - - - -

- Reports{' '} -

- {' '} - ({this.state.reportsTableContent.length}) -

-

-
-
- - - Refresh - - -
- - -
- - - - - -

- Report definitions -

- {' '} - ({this.state.reportDefinitionsTableContent.length}) -

-

-
-
- - - Refresh - - - - { - window.location.assign('opendistro_kibana_reports#/create'); - }} - > - Create - - -
- - -
-
- ); - } + return ( +
+ + + + +

+ Reports{' '} +

({reportsTableContent.length})

+

+
+
+ + + Refresh + + +
+ + +
+ + + + + +

+ Report definitions +

+ {' '} + ({reportDefinitionsTableContent.length}) +

+

+
+
+ + + Refresh + + + + { + window.location.assign('opendistro_kibana_reports#/create'); + }} + > + Create + + +
+ + +
+ +
+ ); } diff --git a/kibana-reports/public/components/main/main_utils.tsx b/kibana-reports/public/components/main/main_utils.tsx index a47ba54c..4eff3fab 100644 --- a/kibana-reports/public/components/main/main_utils.tsx +++ b/kibana-reports/public/components/main/main_utils.tsx @@ -154,6 +154,7 @@ export const readStreamToFile = async ( }; export const generateReport = async (metadata, httpClient) => { + let status = false; await httpClient .post('../api/reporting/generateReport', { body: JSON.stringify(metadata), @@ -165,14 +166,21 @@ export const generateReport = async (metadata, httpClient) => { const fileFormat = extractFileFormat(response['filename']); const fileName = response['filename']; await readStreamToFile(await response['data'], fileFormat, fileName); - return response; + status = true; }) .catch((error) => { console.log('error on generating report:', error); + status = false; }); + return status; }; -export const generateReportById = async (reportId, httpClient) => { +export const generateReportById = async ( + reportId, + httpClient, + handleSuccessToast, + handleErrorToast +) => { await httpClient .post(`../api/reporting/generateReport/${reportId}`) .then(async (response) => { @@ -180,9 +188,11 @@ export const generateReportById = async (reportId, httpClient) => { const fileFormat = extractFileFormat(response['filename']); const fileName = response['filename']; await readStreamToFile(await response['data'], fileFormat, fileName); + handleSuccessToast(); return response; }) .catch((error) => { console.log('error on generating report by id:', error); + handleErrorToast(); }); }; diff --git a/kibana-reports/public/components/main/report_definition_details/report_definition_details.tsx b/kibana-reports/public/components/main/report_definition_details/report_definition_details.tsx index cab2237a..b1d6d351 100644 --- a/kibana-reports/public/components/main/report_definition_details/report_definition_details.tsx +++ b/kibana-reports/public/components/main/report_definition_details/report_definition_details.tsx @@ -28,6 +28,7 @@ import { EuiButton, EuiIcon, EuiLink, + EuiGlobalToastList, } from '@elastic/eui'; import { ReportDetailsComponent } from '../report_details/report_details'; import { fileFormatsUpper, generateReport } from '../main_utils'; @@ -40,8 +41,125 @@ export function ReportDefinitionDetails(props) { reportDefinitionRawResponse, setReportDefinitionRawResponse, ] = useState({}); + const [toasts, setToasts] = useState([]); const reportDefinitionId = props.match['params']['reportDefinitionId']; + const addErrorLoadingDetailsToastHandler = () => { + const errorToast = { + title: 'Error loading report definition details', + color: 'danger', + iconType: 'alert', + id: 'reportDefinitionDetailsErrorToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleDetailsErrorToast = () => { + addErrorLoadingDetailsToastHandler(); + }; + + const addSuccessGeneratingReportToastHandler = () => { + const successToast = { + title: 'Success', + color: 'success', + text:

Report successfully downloaded!

, + id: 'generateReportSuccessToast', + }; + setToasts(toasts.concat(successToast)); + }; + + const handleSuccessGeneratingReportToast = () => { + addSuccessGeneratingReportToastHandler(); + }; + + const addErrorGeneratingReportToastHandler = () => { + const errorToast = { + title: 'Error generating report', + color: 'danger', + iconType: 'alert', + id: 'generateReportErrorToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleErrorGeneratingReportToast = () => { + addErrorGeneratingReportToastHandler(); + }; + + const addSuccessEnablingScheduleToastHandler = () => { + const successToast = { + title: 'Success', + color: 'success', + text:

Schedule successfully enabled!

, + id: 'successEnableToast', + }; + setToasts(toasts.concat(successToast)); + }; + + const handleSuccessEnablingScheduleToast = () => { + addSuccessEnablingScheduleToastHandler(); + }; + + const addErrorEnablingScheduleToastHandler = () => { + const errorToast = { + title: 'Error enabling schedule', + color: 'danger', + iconType: 'alert', + id: 'errorToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleErrorEnablingScheduleToast = () => { + addErrorEnablingScheduleToastHandler(); + }; + + const addSuccessDisablingScheduleToastHandler = () => { + const successToast = { + title: 'Success', + color: 'success', + text:

Schedule successfully disabled!

, + id: 'successDisableToast', + }; + setToasts(toasts.concat(successToast)); + }; + + const handleSuccessDisablingScheduleToast = () => { + addSuccessDisablingScheduleToastHandler(); + }; + + const addErrorDisablingScheduleToastHandler = () => { + const errorToast = { + title: 'Error disabling schedule', + color: 'danger', + iconType: 'alert', + id: 'errorDisableToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleErrorDisablingScheduleToast = () => { + addErrorDisablingScheduleToastHandler(); + }; + + const addErrorDeletingReportDefinitionToastHandler = () => { + const errorToast = { + title: 'Error deleting report definition', + color: 'danger', + iconType: 'alert', + id: 'errorDeleteToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleErrorDeletingReportDefinitionToast = () => { + addErrorDeletingReportDefinitionToastHandler(); + }; + + const removeToast = (removedToast) => { + setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); + }; + const handleReportDefinitionDetails = (e) => { setReportDefinitionDetails(e); }; @@ -52,10 +170,28 @@ export function ReportDefinitionDetails(props) { const getReportDefinitionDetailsMetadata = (data) => { const reportDefinition: ReportDefinitionSchemaType = data.report_definition; - const { report_params: reportParams, trigger, delivery, time_created: timeCreated, last_updated: lastUpdated } = reportDefinition - const { trigger_type: triggerType, trigger_params: triggerParams } = trigger; - const { delivery_type: deliveryType, delivery_params: deliveryParams } = delivery; - const { core_params: { base_url: baseUrl, report_format: reportFormat, time_duration: timeDuration} } = reportParams; + const { + report_params: reportParams, + trigger, + delivery, + time_created: timeCreated, + last_updated: lastUpdated, + } = reportDefinition; + const { + trigger_type: triggerType, + trigger_params: triggerParams, + } = trigger; + const { + delivery_type: deliveryType, + delivery_params: deliveryParams, + } = delivery; + const { + core_params: { + base_url: baseUrl, + report_format: reportFormat, + time_duration: timeDuration, + }, + } = reportParams; let readableDate = new Date(timeCreated); let displayCreatedDate = @@ -85,11 +221,20 @@ export function ReportDefinitionDetails(props) { alertDetails: `\u2014`, channel: deliveryType, status: reportDefinition.status, - kibanaRecipients: deliveryParams.kibana_recipients ? deliveryParams.kibana_recipients : `\u2014`, - emailRecipients: deliveryType === 'Channel' ? deliveryParams.recipients : `\u2014`, - emailSubject: deliveryType === 'Channel' ? deliveryParams.title : `\u2014`, - emailBody: deliveryType === 'Channel' ? deliveryParams.textDescription : `\u2014`, - reportAsAttachment: (deliveryType === 'Channel' && deliveryParams.email_format === 'Attachment')? 'True' : 'False', + kibanaRecipients: deliveryParams.kibana_recipients + ? deliveryParams.kibana_recipients + : `\u2014`, + emailRecipients: + deliveryType === 'Channel' ? deliveryParams.recipients : `\u2014`, + emailSubject: + deliveryType === 'Channel' ? deliveryParams.title : `\u2014`, + emailBody: + deliveryType === 'Channel' ? deliveryParams.textDescription : `\u2014`, + reportAsAttachment: + deliveryType === 'Channel' && + deliveryParams.email_format === 'Attachment' + ? 'True' + : 'False', }; return reportDefinitionDetails; }; @@ -115,6 +260,7 @@ export function ReportDefinitionDetails(props) { }) .catch((error) => { console.error('error when getting report definition details:', error); + handleDetailsErrorToast(); }); }, []); @@ -178,9 +324,19 @@ export function ReportDefinitionDetails(props) { setReportDefinitionDetails( getReportDefinitionDetailsMetadata(updatedRawResponse) ); + if (statusChange === 'Enable') { + handleSuccessEnablingScheduleToast(); + } else if (statusChange === 'Disable') { + handleSuccessDisablingScheduleToast(); + } }) .catch((error) => { console.error('error in updating report definition status:', error); + if (statusChange === 'Enable') { + handleErrorEnablingScheduleToast(); + } else if (statusChange === 'Disable') { + handleErrorDisablingScheduleToast(); + } }); }; @@ -195,7 +351,7 @@ export function ReportDefinitionDetails(props) { ); }; - const generateReportFromDetails = () => { + const generateReportFromDetails = async () => { let duration = reportDefinitionRawResponse.report_definition.report_params.core_params .time_duration; @@ -209,7 +365,15 @@ export function ReportDefinitionDetails(props) { report_definition: reportDefinitionRawResponse.report_definition, }; const { httpClient } = props; - generateReport(onDemandDownloadMetadata, httpClient); + let generateReportSuccess = await generateReport( + onDemandDownloadMetadata, + httpClient + ); + if (generateReportSuccess) { + handleSuccessGeneratingReportToast(); + } else { + handleErrorGeneratingReportToast(); + } }; const deleteReportDefinition = () => { @@ -221,12 +385,18 @@ export function ReportDefinitionDetails(props) { }) .catch((error) => { console.log('error when deleting report definition:', error); + handleErrorDeletingReportDefinitionToast(); }); }; - const showActionButton = (reportDefinitionDetails.triggerType === 'On demand') - ? generateReportFromDetails()}>Generate report - : + const showActionButton = + reportDefinitionDetails.triggerType === 'On demand' ? ( + generateReportFromDetails()}> + Generate report + + ) : ( + + ); return ( @@ -370,9 +540,7 @@ export function ReportDefinitionDetails(props) { + ); diff --git a/kibana-reports/public/components/main/report_details/report_details.tsx b/kibana-reports/public/components/main/report_details/report_details.tsx index 4f901d2e..2f12a034 100644 --- a/kibana-reports/public/components/main/report_details/report_details.tsx +++ b/kibana-reports/public/components/main/report_details/report_details.tsx @@ -31,8 +31,8 @@ import { EuiButton, EuiLink, EuiIcon, + EuiGlobalToastList, } from '@elastic/eui'; -import { ShareModal } from './share_modal/share_modal'; import { fileFormatsUpper } from '../main_utils'; import { ReportSchemaType } from '../../../../server/model'; @@ -55,8 +55,28 @@ export const ReportDetailsComponent = (props) => { export function ReportDetails(props) { const [reportDetails, setReportDetails] = useState({}); + const [toasts, setToasts] = useState([]); + const reportId = props.match['params']['reportId']; + const addErrorToastHandler = () => { + const errorToast = { + title: 'Error loading report details', + color: 'danger', + iconType: 'alert', + id: 'reportDetailsErrorToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleErrorToast = () => { + addErrorToastHandler(); + }; + + const removeToast = (removedToast) => { + setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); + }; + const handleReportDetails = (e) => { setReportDetails(e); }; @@ -71,10 +91,21 @@ export function ReportDetails(props) { }; const getReportDetailsData = (report: ReportSchemaType) => { - const {report_definition: reportDefinition, last_updated: lastUpdated, state, query_url: queryUrl } = report - const { report_params: reportParams, trigger, delivery } = reportDefinition - const { trigger_type: triggerType, trigger_params: triggerParams } = trigger; - const { delivery_type: deliveryType, delivery_params: deliveryParams } = delivery; + const { + report_definition: reportDefinition, + last_updated: lastUpdated, + state, + query_url: queryUrl, + } = report; + const { report_params: reportParams, trigger, delivery } = reportDefinition; + const { + trigger_type: triggerType, + trigger_params: triggerParams, + } = trigger; + const { + delivery_type: deliveryType, + delivery_params: deliveryParams, + } = delivery; const coreParams = reportParams.core_params; // covert timestamp to local date-time string @@ -96,11 +127,20 @@ export function ReportDetails(props) { scheduleDetails: `\u2014`, alertDetails: `\u2014`, channel: deliveryType, - kibanaRecipients: deliveryParams.kibana_recipients ? deliveryParams.kibana_recipients : `\u2014`, - emailRecipients: deliveryType === 'Channel' ? deliveryParams.recipients : `\u2014`, - emailSubject: deliveryType === 'Channel' ? deliveryParams.title : `\u2014`, - emailBody: deliveryType === 'Channel' ? deliveryParams.textDescription : `\u2014`, - reportAsAttachment: (deliveryType === 'Channel' && deliveryParams.email_format === 'Attachment')? 'True' : 'False', + kibanaRecipients: deliveryParams.kibana_recipients + ? deliveryParams.kibana_recipients + : `\u2014`, + emailRecipients: + deliveryType === 'Channel' ? deliveryParams.recipients : `\u2014`, + emailSubject: + deliveryType === 'Channel' ? deliveryParams.title : `\u2014`, + emailBody: + deliveryType === 'Channel' ? deliveryParams.textDescription : `\u2014`, + reportAsAttachment: + deliveryType === 'Channel' && + deliveryParams.email_format === 'Attachment' + ? 'True' + : 'False', queryUrl: queryUrl, }; return reportDetails; @@ -126,6 +166,7 @@ export function ReportDetails(props) { }) .catch((error) => { console.log('Error when fetching report details: ', error); + handleErrorToast(); }); }, []); @@ -281,6 +322,11 @@ export function ReportDetails(props) { + ); diff --git a/kibana-reports/public/components/main/reports_table.tsx b/kibana-reports/public/components/main/reports_table.tsx index 6d1a9580..52caf3ee 100644 --- a/kibana-reports/public/components/main/reports_table.tsx +++ b/kibana-reports/public/components/main/reports_table.tsx @@ -70,7 +70,14 @@ const emptyMessageReports = ( ); export function ReportsTable(props) { - const { getRowProps, pagination, reportsTableItems, httpClient } = props; + const { + getRowProps, + pagination, + reportsTableItems, + httpClient, + handleSuccessToast, + handleErrorToast, + } = props; const [sortField, setSortField] = useState('timeCreated'); const [sortDirection, setSortDirection] = useState(SortDirection.desc); @@ -132,7 +139,12 @@ export function ReportsTable(props) { const onDemandDownload = async (id: any) => { handleLoading(true); - await generateReportById(id, httpClient); + await generateReportById( + id, + httpClient, + handleSuccessToast, + handleErrorToast + ); handleLoading(false); }; diff --git a/kibana-reports/public/components/report_definitions/edit/edit_report_definition.tsx b/kibana-reports/public/components/report_definitions/edit/edit_report_definition.tsx index 34b566c1..df8ab96e 100644 --- a/kibana-reports/public/components/report_definitions/edit/edit_report_definition.tsx +++ b/kibana-reports/public/components/report_definitions/edit/edit_report_definition.tsx @@ -23,12 +23,33 @@ import { EuiTitle, EuiPageBody, EuiSpacer, + EuiGlobalToastList, } from '@elastic/eui'; import { ReportSettings } from '../report_settings'; import { ReportDelivery } from '../delivery'; import { ReportTrigger } from '../report_trigger'; export function EditReportDefinition(props) { + const [toasts, setToasts] = useState([]); + + const addErrorUpdatingReportDefinitionToast = () => { + const errorToast = { + title: 'Error updating report definition', + color: 'danger', + iconType: 'alert', + id: 'errorToast', + }; + setToasts(toasts.concat(errorToast)); + }; + + const handleErrorUpdatingReportDefinitionToast = () => { + addErrorUpdatingReportDefinitionToast(); + }; + + const removeToast = (removedToast) => { + setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); + }; + const reportDefinitionId = props['match']['params']['reportDefinitionId']; let editReportDefinitionRequest = { report_params: { @@ -67,6 +88,7 @@ export function EditReportDefinition(props) { }) .catch((error) => { console.error('error in updating report definition:', error); + handleErrorUpdatingReportDefinitionToast(); }); }; @@ -147,6 +169,11 @@ export function EditReportDefinition(props) { + );