/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ import { EuiBreadcrumb, EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiInMemoryTable, EuiLink, EuiOverlayMask, EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageHeader, EuiPageHeaderSection, EuiPopover, EuiSpacer, EuiTableFieldDataColumnType, EuiText, EuiTitle, } from '@elastic/eui'; import React, { ReactElement, useEffect, useState } from 'react'; import moment from 'moment'; import _ from 'lodash'; import { useHistory, useLocation } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { coreRefs } from '../../framework/core_refs'; import { ChromeBreadcrumb } from '../../../../../src/core/public'; import { CREATE_PANEL_MESSAGE, CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_DOCUMENTATION_URL, } from '../../../common/constants/custom_panels'; import { UI_DATE_FORMAT } from '../../../common/constants/shared'; import { getCustomModal } from './helpers/modal_containers'; import { CustomPanelListType, CustomPanelType } from '../../../common/types/custom_panels'; import { getSampleDataModal } from '../common/helpers/add_sample_modal'; import { pageStyles } from '../../../common/constants/shared'; import { DeleteModal } from '../common/helpers/delete_modal'; import { createPanel, deletePanels, fetchPanels, isUuid, newPanelTemplate, renameCustomPanel, selectPanelList, } from './redux/panel_slice'; import { isNameValid } from './helpers/utils'; import { useToast } from '../common/toast'; /* * "CustomPanelTable" module, used to view all the saved panels * * Props taken in as params are: * loading: loader bool for the table * fetchCustomPanels: fetch panels function * customPanels: List of panels available * createCustomPanel: create panel function * setBreadcrumbs: setter for breadcrumbs on top panel * parentBreadcrumb: parent breadcrumb * renameCustomPanel: rename function for the panel * cloneCustomPanel: clone function for the panel * deleteCustomPanelList: delete function for the panels */ interface Props { loading: boolean; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; parentBreadcrumbs: EuiBreadcrumb[]; addSamplePanels: () => void; } export const CustomPanelTable = ({ loading, setBreadcrumbs, parentBreadcrumbs, addSamplePanels, }: Props) => { const customPanels = useSelector(selectPanelList); const [isModalVisible, setIsModalVisible] = useState(false); // Modal Toggle const [modalLayout, setModalLayout] = useState(); // Modal Layout const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [selectedCustomPanels, setselectedCustomPanels] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const location = useLocation(); const history = useHistory(); const dispatch = useDispatch(); const { setToast } = useToast(); useEffect(() => { setBreadcrumbs(parentBreadcrumbs); dispatch(fetchPanels()); }, []); useEffect(() => { const url = window.location.hash.split('/'); if (url[url.length - 1] === 'create') { createPanelModal(); } }, [location]); const closeModal = () => { setIsModalVisible(false); }; const showModal = () => { setIsModalVisible(true); }; const onCreate = async (newCustomPanelName: string) => { if (!isNameValid(newCustomPanelName)) { setToast('Invalid Dashboard name', 'danger'); } else { const newPanel = newPanelTemplate(newCustomPanelName); dispatch(createPanel(newPanel)); } closeModal(); }; const onRename = async (newCustomPanelName: string) => { if (!isNameValid(newCustomPanelName)) { setToast('Invalid Dashboard name', 'danger'); } else { dispatch(renameCustomPanel(newCustomPanelName, selectedCustomPanels[0].id)); } closeModal(); }; const onClone = async (newName: string) => { if (!isNameValid(newName)) { setToast('Invalid Operational Panel name', 'danger'); } else { let sourcePanel = selectedCustomPanels[0]; try { if (!isUuid(sourcePanel.id)) { // Observability Panel API returns partial record, so for duplication // we will retrieve the entire record and allow new process to continue. const legacyFetchResult = await coreRefs.http!.get( `${CUSTOM_PANELS_API_PREFIX}/panels/${sourcePanel.id}` ); sourcePanel = legacyFetchResult.operationalPanel; } const { id, ...newPanel } = { ...sourcePanel, title: newName, }; dispatch(createPanel(newPanel)); } catch (err) { setToast( 'Error cloning Observability Dashboard, please make sure you have the correct permission.', 'danger' ); console.error(err); } } closeModal(); }; const onDelete = async () => { dispatch(deletePanels(selectedCustomPanels)); closeModal(); }; const createPanelModal = () => { setModalLayout( getCustomModal( onCreate, () => { closeModal(); history.goBack(); }, 'Name', 'Create Observability Dashboard', 'Cancel', 'Create', undefined, CREATE_PANEL_MESSAGE ) ); showModal(); }; const renamePanel = () => { setModalLayout( getCustomModal( onRename, closeModal, 'Name', 'Rename Dashboard', 'Cancel', 'Rename', selectedCustomPanels[0].title, CREATE_PANEL_MESSAGE ) ); showModal(); }; const clonePanelModal = () => { setModalLayout( getCustomModal( onClone, closeModal, 'Name', 'Duplicate Dashboard', 'Cancel', 'Duplicate', selectedCustomPanels[0].title + ' (copy)', CREATE_PANEL_MESSAGE ) ); showModal(); }; const deletePanel = () => { const customPanelString = `Observability Dashboard${ selectedCustomPanels.length > 1 ? 's' : '' }`; setModalLayout( ); showModal(); }; const addSampledata = async () => { setModalLayout( getSampleDataModal(closeModal, async () => { closeModal(); await addSamplePanels(); }) ); showModal(); }; const popoverButton = ( setIsActionsPopoverOpen(!isActionsPopoverOpen)} > Actions ); const popoverItems = (): ReactElement[] => [ { setIsActionsPopoverOpen(false); renamePanel(); }} > Rename , { setIsActionsPopoverOpen(false); clonePanelModal(); }} > Duplicate , { setIsActionsPopoverOpen(false); deletePanel(); }} > Delete , { setIsActionsPopoverOpen(false); addSampledata(); }} > Add samples , ]; const tableColumns = [ { field: 'title', name: 'Name', sortable: true, truncateText: true, render: (value, record) => ( {_.truncate(value, { length: 100 })} ), }, { field: 'dateModified', name: 'Last updated', sortable: true, render: (value) => moment(new Date(value)).format(UI_DATE_FORMAT), }, { field: 'dateCreated', name: 'Created', sortable: true, render: (value) => moment(new Date(value)).format(UI_DATE_FORMAT), }, ] as Array>; return (

Observability dashboards

Dashboard ({customPanels.length})

Use Observability Dashboard to create and view different visualizations on ingested observability data, using PPL (Piped Processing Language) queries.{' '} Learn more
setIsActionsPopoverOpen(false)} > Create Dashboard
{customPanels.length > 0 ? ( <> setSearchQuery(e.target.value)} /> customPanel.title.toLowerCase().includes(searchQuery.toLowerCase()) ) : customPanels } itemId="id" columns={tableColumns} tableLayout="auto" pagination={{ initialPageSize: 10, pageSizeOptions: [8, 10, 13], }} sorting={{ sort: { field: 'dateModified', direction: 'desc', }, }} allowNeutralSort={false} isSelectable={true} selection={{ onSelectionChange: (items) => setselectedCustomPanels(items), }} /> ) : ( <>

No Observability Dashboards

Use Observability Dashboards to dive deeper into observability
using PPL queries and insightful visualizations
Create Dashboard addSampledata()}> Add samples )}
{isModalVisible && modalLayout}
); };