/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ import React, { ReactChild, useEffect, useState } from 'react'; import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import DSLService from 'public/services/requests/dsl'; import PPLService from 'public/services/requests/ppl'; import SavedObjects from 'public/services/saved_objects/event_analytics/saved_objects'; import TimestampUtils from 'public/services/timestamp/timestamp'; import { EuiGlobalToastList, EuiLink } from '@elastic/eui'; import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; import { isEmpty, last } from 'lodash'; import { useDispatch } from 'react-redux'; import { AppTable } from './components/app_table'; import { Application } from './components/application'; import { CreateApp } from './components/create'; import { TraceAnalyticsComponentDeps, TraceAnalyticsCoreDeps } from '../trace_analytics/home'; import { FilterType } from '../trace_analytics/components/common/filters/filters'; import { handleDataPrepperIndicesExistRequest } from '../trace_analytics/requests/request_handler'; import { ChromeBreadcrumb, NotificationsStart } from '../../../../../src/core/public'; import { APP_ANALYTICS_API_PREFIX } from '../../../common/constants/application_analytics'; import { ApplicationRequestType, ApplicationType, } from '../../../common/types/application_analytics'; import { calculateAvailability, fetchPanelsVizIdList, isNameValid, removeTabData, } from './helpers/utils'; import { CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_DOCUMENTATION_URL, } from '../../../common/constants/custom_panels'; import { QueryManager } from '../../../common/query_manager/ppl_query_manager'; import { observabilityApplicationsID } from '../../../common/constants/shared'; export type AppAnalyticsCoreDeps = TraceAnalyticsCoreDeps; interface HomeProps extends RouteComponentProps, AppAnalyticsCoreDeps { pplService: PPLService; dslService: DSLService; savedObjects: SavedObjects; timestampUtils: TimestampUtils; notifications: NotificationsStart; queryManager: QueryManager; parentBreadcrumbs: ChromeBreadcrumb[]; } export interface AppAnalyticsComponentDeps extends TraceAnalyticsComponentDeps { name: string; description: string; setNameWithStorage: (newName: string) => void; setDescriptionWithStorage: (newDescription: string) => void; setQueryWithStorage: (newQuery: string) => void; setFiltersWithStorage: (newFilters: FilterType[]) => void; setAppConfigs: (newAppConfigs: FilterType[]) => void; parentBreadcrumbs: ChromeBreadcrumb[]; } export const Home = (props: HomeProps) => { const { pplService, dslService, timestampUtils, savedObjects, parentBreadcrumbs, http, chrome, notifications, queryManager, } = props; const [triggerSwitchToEvent, setTriggerSwitchToEvent] = useState(0); const dispatch = useDispatch(); const [applicationList, setApplicationList] = useState([]); const [toasts, setToasts] = useState([]); const [indicesExist, setIndicesExist] = useState(true); const [appConfigs, setAppConfigs] = useState([]); const storedFilters = sessionStorage.getItem('AppAnalyticsFilters'); const [filters, setFilters] = useState( storedFilters ? JSON.parse(storedFilters) : [] ); const [name, setName] = useState(sessionStorage.getItem('AppAnalyticsName') || ''); const [description, setDescription] = useState( sessionStorage.getItem('AppAnalyticsDescription') || '' ); const [query, setQuery] = useState(sessionStorage.getItem('AppAnalyticsQuery') || ''); const [startTime, setStartTime] = useState( sessionStorage.getItem('AppAnalyticsStartTime') || 'now-24h' ); const [endTime, setEndTime] = useState( sessionStorage.getItem('AppAnalyticsEndTime') || 'now' ); // Setting state with storage to save input when user refreshes page const setFiltersWithStorage = (newFilters: FilterType[]) => { setFilters(newFilters); sessionStorage.setItem('AppAnalyticsFilters', JSON.stringify(newFilters)); }; const setNameWithStorage = (newName: string) => { setName(newName); sessionStorage.setItem('AppAnalyticsName', newName); }; const setDescriptionWithStorage = (newDescription: string) => { setDescription(newDescription); sessionStorage.setItem('AppAnalyticsDescription', newDescription); }; const setQueryWithStorage = (newQuery: string) => { setQuery(newQuery); sessionStorage.setItem('AppAnalyticsQuery', newQuery); }; useEffect(() => { handleDataPrepperIndicesExistRequest(http, setIndicesExist); }, []); const commonProps: AppAnalyticsComponentDeps = { parentBreadcrumbs, http, chrome, name, setNameWithStorage, description, setDescriptionWithStorage, query, setQuery, setQueryWithStorage, appConfigs, setAppConfigs, filters, setFilters, setFiltersWithStorage, startTime, setStartTime, endTime, setEndTime, mode: 'data_prepper', dataPrepperIndicesExist: indicesExist, }; const setToast = (title: string, color = 'success', text?: ReactChild) => { if (!text) text = ''; setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]); }; const clearStorage = () => { setNameWithStorage(''); setDescriptionWithStorage(''); setFiltersWithStorage([]); setQueryWithStorage(''); }; const moveToApp = (id: string, type: string) => { window.location.assign(`${observabilityApplicationsID}#/${id}`); if (type === 'createSetAvailability') { setTriggerSwitchToEvent(2); } }; const createPanelForApp = (applicationId: string, appName: string, type: string) => { return http .post(`${CUSTOM_PANELS_API_PREFIX}/panels`, { body: JSON.stringify({ panelName: `${appName}'s Panel`, applicationId, }), }) .then((res) => { updateApp(applicationId, { panelId: res.newPanelId }, type); }) .catch((err) => { setToast( 'Please ask your administrator to enable Operational Panels for you.', 'danger', Documentation ); console.error(err); }); }; const deletePanelForApp = (appPanelId: string) => { const concatList = [appPanelId].toString(); return http.delete(`${CUSTOM_PANELS_API_PREFIX}/panelList/` + concatList).catch((err) => { setToast( 'Error occurred while deleting Operational Panels, please make sure you have the correct permission.', 'danger' ); console.error(err.body.message); }); }; const deleteSavedVisualizationsForPanel = async (appPanelId: string) => { const savedVizIdsToDelete = await fetchPanelsVizIdList(http, appPanelId); if (!isEmpty(savedVizIdsToDelete)) { savedObjects .deleteSavedObjectsList({ objectIdList: savedVizIdsToDelete }) .then((res) => { deletePanelForApp(appPanelId); }) .catch((err) => { setToast('Error occurred while deleting Saved Visualizations', 'danger'); console.error(err); }); } }; // Fetches all existing applications const fetchApps = () => { return http .get(`${APP_ANALYTICS_API_PREFIX}/`) .then(async (res) => { // Want to calculate availability going down the table const availabilityVisIdStore: Record = {}; for (let i = 0; i < res.data.length; i++) { availabilityVisIdStore[res.data[i].id] = res.data[i].availability.availabilityVisId; res.data[i].availability = { name: '', color: 'loading', availabilityVisId: '' }; } setApplicationList(res.data); for (let i = res.data.length - 1; i > -1; i--) { res.data[i].availability = await calculateAvailability( http, pplService, res.data[i], availabilityVisIdStore[res.data[i].id], () => {} ); // Need to set state with new object to trigger re-render setApplicationList([ ...res.data.filter((app: ApplicationType) => app.id !== res.data[i].id), res.data[i], ]); } }) .catch((err) => { setToast('Error occurred while fetching applications', 'danger'); console.error(err); }); }; // Create a new application const createApp = (application: ApplicationRequestType, type: string) => { const toast = isNameValid( application.name, applicationList.map((obj) => obj.name) ); if (toast.length > 0) { setToast(toast.join(', '), 'danger'); return; } const requestBody = { name: application.name, description: application.description || '', baseQuery: application.baseQuery, servicesEntities: application.servicesEntities, traceGroups: application.traceGroups, availabilityVisId: '', }; return http .post(`${APP_ANALYTICS_API_PREFIX}/`, { body: JSON.stringify(requestBody), }) .then(async (res) => { createPanelForApp(res.newAppId, application.name, type); setToast(`Application "${application.name}" successfully created!`); clearStorage(); }) .catch((err) => { setToast(`Error occurred while creating new application "${application.name}"`, 'danger'); console.error(err); }); }; // Rename an existing application const renameApp = (newAppName: string, appId: string) => { const toast = isNameValid( newAppName, applicationList.map((obj) => obj.name) ); if (toast.length > 0) { setToast(toast.join(', '), 'danger'); return; } const requestBody = { appId, name: newAppName, }; return http .put(`${APP_ANALYTICS_API_PREFIX}/rename`, { body: JSON.stringify(requestBody), }) .then((res) => { setApplicationList((prevApplicationList) => { const newApplicationData = [...prevApplicationList]; const renamedApplication = newApplicationData.find( (application) => application.id === appId ); if (renamedApplication) renamedApplication.name = newAppName; return newApplicationData; }); setToast(`Application successfully renamed to "${newAppName}"`); }) .catch((err) => { setToast('Error occurred while renaming application', 'danger'); console.error(err); }); }; // Update existing application const updateApp = ( appId: string, updateAppData: Partial, type: string ) => { const requestBody = { appId, updateBody: updateAppData, }; return http .put(`${APP_ANALYTICS_API_PREFIX}/`, { body: JSON.stringify(requestBody), }) .then((res) => { if (type === 'update') { setToast('Application successfully updated.'); clearStorage(); moveToApp(res.updatedAppId, type); } if (type.startsWith('create')) { moveToApp(res.updatedAppId, type); } }) .catch((err) => { setToast('Error occurred while updating application', 'danger'); console.error(err); }); }; // Delete existing applications const deleteApp = (appList: string[], panelList: string[], toastMessage?: string) => { return http .delete(`${APP_ANALYTICS_API_PREFIX}/${appList.join(',')}`) .then((res) => { setApplicationList((prevApplicationList) => { return prevApplicationList.filter((app) => !appList.includes(app.id)); }); for (let i = 0; i < appList.length; i++) { removeTabData(dispatch, appList[i], ''); } for (let i = 0; i < panelList.length; i++) { deleteSavedVisualizationsForPanel(panelList[i]); } const message = toastMessage || `Application${appList.length > 1 ? 's' : ''} successfully deleted!`; setToast(message); return res; }) .catch((err: any) => { setToast('Error occured while deleting application', 'danger'); console.error(err); }); }; const callback = (childFunc: () => void) => { if (childFunc && triggerSwitchToEvent > 0) { childFunc(); setTriggerSwitchToEvent(triggerSwitchToEvent - 1); } }; return (
{ setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); }} toastLifeTimeMs={6000} /> ( )} /> ( )} /> ( )} />
); };