/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ import { EuiButton, EuiButtonIcon, EuiCallOut, EuiCodeBlock, EuiDatePicker, EuiDatePickerRange, EuiFlexGroup, EuiFlexItem, EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, EuiFormRow, EuiIcon, EuiLoadingChart, EuiModal, EuiModalBody, EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, EuiSelect, EuiSelectOption, EuiSpacer, EuiText, EuiTitle, EuiToolTip, ShortDate, } from '@elastic/eui'; import _ from 'lodash'; import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { CoreStart } from '../../../../../../../src/core/public'; import { SAVED_VISUALIZATION } from '../../../../../common/constants/explorer'; import { PPLResponse, SavedVisualizationType, VisualizationType, VizContainerError, } from '../../../../../common/types/custom_panels'; import { uiSettingsService } from '../../../../../common/utils'; import PPLService from '../../../../services/requests/ppl'; import { SavedObjectsActions } from '../../../../services/saved_objects/saved_object_client/saved_objects_actions'; import { ObservabilitySavedVisualization } from '../../../../services/saved_objects/saved_object_client/types'; import { FlyoutContainers } from '../../../common/flyout_containers'; import { convertDateTime, displayVisualization, getQueryResponse, isDateValid, parseSavedVisualizations, } from '../../helpers/utils'; import { replaceVizInPanel, selectPanel } from '../../redux/panel_slice'; import './visualization_flyout.scss'; import { useToast } from '../../../common/toast'; /* * VisaulizationFlyoutSO - This module create a flyout to add visualization for SavedObjects custom Panels * * Props taken in as params are: * panelId: panel Id of current Observability Dashboard * closeFlyout: function to close the flyout * start: start time in date filter * end: end time in date filter * savedObjects: savedObjects core service * pplService: ppl requestor service * setPanelVisualizations: function set the visualization list in panel * isFlyoutReplacement: boolean to see if the flyout is trigger for add or replace visualization * replaceVisualizationId: string id of the visualization to be replaced */ interface VisualizationFlyoutSOProps { panelId: string; pplFilterValue: string; closeFlyout: () => void; start: ShortDate; end: ShortDate; http: CoreStart['http']; savedObjects: CoreStart['savedObjects']; pplService: PPLService; setPanelVisualizations: React.Dispatch>; isFlyoutReplacement?: boolean | undefined; replaceVisualizationId?: string | undefined; appId?: string; addVisualizationPanel: any; } export const VisaulizationFlyoutSO = ({ appId = '', pplFilterValue, closeFlyout, start, end, pplService, isFlyoutReplacement, replaceVisualizationId, addVisualizationPanel, }: VisualizationFlyoutSOProps) => { const dispatch = useDispatch(); const { setToast } = useToast(); const panel = useSelector(selectPanel); const [newVisualizationTitle, setNewVisualizationTitle] = useState(''); const [newVisualizationType, setNewVisualizationType] = useState(''); const [newVisualizationTimeField, setNewVisualizationTimeField] = useState(''); const [previewMetaData, setPreviewMetaData] = useState(); const [pplQuery, setPPLQuery] = useState(''); const [previewData, setPreviewData] = useState({} as PPLResponse); const [previewArea, setPreviewArea] = useState(<>); const [previewLoading, setPreviewLoading] = useState(false); const [isPreviewError, setIsPreviewError] = useState({} as VizContainerError); const [savedVisualizations, setSavedVisualizations] = useState([]); const [visualizationOptions, setVisualizationOptions] = useState([]); const [selectValue, setSelectValue] = useState(''); // DateTimePicker States const startDate = convertDateTime(start, true, false); const endDate = convertDateTime(end, false, false); const [isModalVisible, setIsModalVisible] = useState(false); const [modalContent, setModalContent] = useState(<>); const closeModal = () => setIsModalVisible(false); const showModal = (modalType: string) => { setModalContent(

{isPreviewError.errorMessage}

Error Details {isPreviewError.errorDetails} Close
); setIsModalVisible(true); }; const isInputValid = () => { if (!isDateValid(convertDateTime(start), convertDateTime(end, false), setToast)) { return false; } if (selectValue === '') { setToast('Please make a valid selection', 'danger', undefined); return false; } return true; }; const addVisualization = () => { if (!isInputValid()) return; if (isFlyoutReplacement) { dispatch( replaceVizInPanel(panel, replaceVisualizationId, selectValue, newVisualizationTitle) ); } else { const visualizationsWithNewPanel = addVisualizationPanel({ savedVisualizationId: selectValue, onSuccess: `Visualization ${newVisualizationTitle} successfully added!`, onFailure: `Error in adding ${newVisualizationTitle} visualization to the panel`, }); } setToast(`Visualization ${newVisualizationTitle} successfully added!`); closeFlyout(); }; const onRefreshPreview = () => { if (!isInputValid()) return; getQueryResponse( pplService, pplQuery, newVisualizationType, start, end, setPreviewData, setPreviewLoading, setIsPreviewError, pplFilterValue, newVisualizationTimeField ); }; const timeRange = ( endDate} // date-picker-preview style reduces height, need to add an empty line // above error message so it does not overlap with DatePicker. error={['', 'Time range is invalid.']} > endDate} aria-label="Start date" dateFormat={uiSettingsService.get('dateFormat')} /> } endDateControl={ endDate} aria-label="End date" dateFormat={uiSettingsService.get('dateFormat')} /> } /> ); const flyoutHeader = (

{isFlyoutReplacement ? 'Replace visualization' : 'Select existing visualization'}

); const onChangeSelection = (e: React.ChangeEvent) => { setSelectValue(e.target.value); }; const emptySavedVisualizations = (

No saved visualizations found!

); const flyoutBody = savedVisualizations.length > 0 ? ( <> onChangeSelection(e)} options={visualizationOptions} value={selectValue} />

Preview

{previewArea}
) : ( <>
{'Please use the "create new visualization" option in add visualization menu.'}
); const flyoutFooter = ( Cancel Add ); // Fetch all saved visualizations const fetchSavedVisualizations = async () => { return SavedObjectsActions.getBulk({ objectType: [SAVED_VISUALIZATION], sortOrder: 'desc', fromIndex: 0, }) .then((response) => ({ visualizations: response.observabilityObjectList.map(parseSavedVisualizations), })) .then((res) => { if (res.visualizations.length > 0) { setSavedVisualizations(res.visualizations); const filterAppVis = res.visualizations.filter((vis: SavedVisualizationType) => { return appId ? vis.hasOwnProperty('application_id') ? vis.application_id === appId : false : !vis.hasOwnProperty('application_id'); }); setVisualizationOptions( filterAppVis.map((visualization: SavedVisualizationType) => { return { value: visualization.id, text: visualization.name }; }) ); } }) .catch((err) => { console.error('Issue in fetching the operational panels', err); }); }; useEffect(() => { const previewTemplate = ( <> {timeRange} {previewLoading ? ( ) : !_.isEmpty(isPreviewError) ? (

{isPreviewError.errorMessage}

{isPreviewError.hasOwnProperty('errorDetails') && isPreviewError.errorDetails !== '' ? ( showModal('errorModal')} size="s"> See error details ) : ( <> )}
) : (
{displayVisualization(previewMetaData, previewData, newVisualizationType)}
)}
); setPreviewArea(previewTemplate); }, [previewLoading]); // On change of selected visualization change options useEffect(() => { for (let i = 0; i < savedVisualizations.length; i++) { const visualization = savedVisualizations[i]; if (visualization.id === selectValue) { setPPLQuery(visualization.query); setNewVisualizationTitle(visualization.name); setNewVisualizationType(visualization.type); setPreviewMetaData(visualization); setNewVisualizationTimeField(visualization.timeField); break; } } }, [selectValue]); // load saved visualizations useEffect(() => { fetchSavedVisualizations(); }, []); return ( <> {isModalVisible && modalContent} ); };