/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import { EuiDescriptionList, EuiSelectOption, EuiSpacer, EuiText } from '@elastic/eui'; import { ApplicationType, AvailabilityType } from 'common/types/application_analytics'; import { FilterType } from 'public/components/trace_analytics/components/common/filters/filters'; import PPLService from 'public/services/requests/ppl'; import React, { Dispatch, ReactChild } from 'react'; import { batch } from 'react-redux'; import { HttpSetup } from '../../../../../../src/core/public'; import { APP_ANALYTICS_API_PREFIX } from '../../../../common/constants/application_analytics'; import { CUSTOM_PANELS_API_PREFIX } from '../../../../common/constants/custom_panels'; import { NEW_SELECTED_QUERY_TAB, TAB_CREATED_TYPE } from '../../../../common/constants/explorer'; import { SPAN_REGEX } from '../../../../common/constants/shared'; import { VisualizationType } from '../../../../common/types/custom_panels'; import { IField } from '../../../../common/types/explorer'; import { preprocessQuery } from '../../../../common/utils/query_utils'; import { fetchVisualizationById } from '../../../components/custom_panels/helpers/utils'; import { init as initFields, remove as removefields, } from '../../event_analytics/redux/slices/field_slice'; import { init as initPatterns, remove as removePatterns, } from '../../event_analytics/redux/slices/patterns_slice'; import { init as initQueryResult, remove as removeQueryResult, } from '../../event_analytics/redux/slices/query_result_slice'; import { changeQuery, init as initQuery, remove as removeQuery, } from '../../event_analytics/redux/slices/query_slice'; import { addTab, removeTab } from '../../event_analytics/redux/slices/query_tab_slice'; import { init as initVisualizationConfig, reset as resetVisualizationConfig, } from '../../event_analytics/redux/slices/viualization_config_slice'; // Name validation export const isNameValid = (name: string, existingNames: string[]) => { const toast: string[] = []; if (name.length >= 50) { toast.push('Name must be less than 50 characters.'); } if (name.trim().length === 0) { toast.push('Name must not be empty.'); } if (existingNames.includes(name)) { toast.push('Name must be unique.'); } return toast; }; export const getListItem = (title: string, description: string | React.ReactElement) => { const titleComponent = ( {title} ); const descriptionComponent = ( {description} ); return (
); }; // Fetch application by id export const fetchAppById = async ( http: HttpSetup, pplService: PPLService, applicationId: string, setApplication: (application: ApplicationType) => void, setFilters: (filters: FilterType[]) => void, setVisWithAvailability: (visList: EuiSelectOption[]) => void, setToasts: (title: string, color?: string, text?: ReactChild) => void ) => { return http .get(`${APP_ANALYTICS_API_PREFIX}/${applicationId}`) .then(async (res: ApplicationType) => { res.availability.availabilityVisId = ( await calculateAvailability( http, pplService, res, res.availability.availabilityVisId, setVisWithAvailability ) ).availabilityVisId; setApplication(res); const serviceFilters = res.servicesEntities.map((ser: string) => { return { field: 'serviceName', operator: 'is', value: ser, inverted: false, disabled: false, }; }); const traceFilters = res.traceGroups.map((tra: string) => { return { field: 'traceGroup', operator: 'is', value: tra, inverted: false, disabled: false, }; }); setFilters([...serviceFilters, ...traceFilters]); }) .catch((err) => { setToasts('Error occurred while fetching application', 'danger'); console.error(err); }); }; // Remove tab data when closed export const removeTabData = ( dispatch: Dispatch, TabIdToBeClosed: string, newIdToFocus: string ) => { batch(() => { dispatch(removeQuery({ tabId: TabIdToBeClosed })); dispatch(removefields({ tabId: TabIdToBeClosed })); dispatch(removeQueryResult({ tabId: TabIdToBeClosed })); dispatch(resetVisualizationConfig({ tabId: TabIdToBeClosed })); dispatch( removeTab({ tabId: TabIdToBeClosed, [NEW_SELECTED_QUERY_TAB]: newIdToFocus, }) ); dispatch(removePatterns({ tabId: TabIdToBeClosed })); }); }; // Create a new tab and initialize its data export const initializeTabData = async (dispatch: Dispatch, tabId: string, where: string) => { await batch(() => { dispatch(initQuery({ tabId })); dispatch(initQueryResult({ tabId })); dispatch(initFields({ tabId })); dispatch(addTab({ tabId })); dispatch(initVisualizationConfig({ tabId })); dispatch( changeQuery({ tabId, query: { [TAB_CREATED_TYPE]: where, }, }) ); dispatch(initPatterns({ tabId })); }); }; export const fetchPanelsVizIdList = async (http: HttpSetup, appPanelId: string) => { return await http .get(`${CUSTOM_PANELS_API_PREFIX}/panels/${appPanelId}`) .then((res) => { const visIds = res.operationalPanel.visualizations.map( (viz: VisualizationType) => viz.savedVisualizationId ); return visIds; }) .catch((err) => { console.error('Error occurred while fetching visualizations for panel', err); return []; }); }; export const calculateAvailability = async ( http: HttpSetup, pplService: PPLService, application: ApplicationType, availabilityVisId: string, setVisWithAvailability: (visList: EuiSelectOption[]) => void ): Promise => { let availability = { name: '', color: '', availabilityVisId: '' }; const panelId = application.panelId; if (!panelId) return availability; // Fetches saved visualizations associated to application's panel // Order visualizations by most recently created const savedVisualizationsIds = (await fetchPanelsVizIdList(http, panelId)).reverse(); if (!savedVisualizationsIds) return availability; const visWithAvailability = []; let availabilityFound = false; for (let i = 0; i < savedVisualizationsIds.length; i++) { const visualizationId = savedVisualizationsIds[i]; // Fetches data for visualization const visData = await fetchVisualizationById(http, visualizationId, (value: string) => console.error(value) ); const userConfigs = visData.user_configs ? JSON.parse(visData.user_configs) : {}; // If there are levels, we get the current value if (userConfigs.availabilityConfig?.hasOwnProperty('level')) { // For every saved visualization with availability levels we push it to visWithAvailability // This is used to populate the options in configuration visWithAvailability.push({ value: visualizationId, text: visData.name }); const levels = userConfigs.availabilityConfig.level.reverse(); let currValue = Number.MIN_VALUE; const finalQuery = preprocessQuery({ rawQuery: visData.query, startTime: visData.selected_date_range.start, endTime: visData.selected_date_range.end, timeField: visData.timeField, isLiveQuery: false, }); await pplService .fetch({ query: finalQuery, format: 'viz', }) .then((res) => { const stat = res.metadata.fields.filter( (field: IField) => !field.name.match(SPAN_REGEX) )[0].name; const value = res.data[stat]; currValue = value[value.length - 1]; }) .catch((err) => { console.error(err); }); for (let j = 0; j < levels.length; j++) { const level = levels[j]; // If there is an availiabilityVisId selected we only want to compute availability based on that if (availabilityVisId ? availabilityVisId === visualizationId : true) { if (level.value !== null) { if (currValue === null) { availability = { name: '', color: 'null', availabilityVisId: '', }; } else { if (!availabilityFound) { const expression = level.expression; switch (expression) { case '≥': if (currValue >= parseFloat(level.value)) { availability = { name: level.name, color: level.color, availabilityVisId: visualizationId, }; availabilityFound = true; } break; case '≤': if (currValue <= parseFloat(level.value)) { availability = { name: level.name, color: level.color, availabilityVisId: visualizationId, }; availabilityFound = true; } break; case '>': if (currValue > parseFloat(level.value)) { availability = { name: level.name, color: level.color, availabilityVisId: visualizationId, }; availabilityFound = true; } break; case '<': if (currValue < parseFloat(level.value)) { availability = { name: level.name, color: level.color, availabilityVisId: visualizationId, }; availabilityFound = true; } break; case '=': if (currValue === parseFloat(level.value)) { availability = { name: level.name, color: level.color, availabilityVisId: visualizationId, }; availabilityFound = true; } break; case '≠': if (currValue !== parseFloat(level.value)) { availability = { name: level.name, color: level.color, availabilityVisId: visualizationId, }; availabilityFound = true; } break; } } } } } } } } setVisWithAvailability(visWithAvailability); if (!availabilityFound && visWithAvailability.length > 0) { return { name: '', color: 'undefined', availabilityVisId: '' }; } return availability; };