From 42e52c22a368006a6781d3e2d6046d3de853ea4c Mon Sep 17 00:00:00 2001 From: Eugene Lee Date: Wed, 27 Apr 2022 11:04:52 -0700 Subject: [PATCH 01/14] Add availability level tab to all chart types, add set availability buttons Signed-off-by: Eugene Lee --- .../common/types/explorer.ts | 3 + .../components/app_table.tsx | 36 ++-- .../components/application.tsx | 29 ++- .../components/configuration.tsx | 24 +-- .../components/create.tsx | 22 ++- .../application_analytics/helpers/types.tsx | 10 + .../application_analytics/helpers/utils.tsx | 158 +++++++-------- .../components/application_analytics/home.tsx | 33 +++- .../event_analytics/explorer/explorer.tsx | 184 ++++++++++-------- .../explorer/sidebar/sidebar.tsx | 1 + .../config_panel/config_panel.tsx | 77 +++++--- .../config_controls/config_availability.tsx | 176 +++++++++++++++++ .../config_controls/config_thresholds.tsx | 52 ++--- .../config_panel/config_panes/json_editor.tsx | 3 +- .../visualizations/charts/bar/bar.tsx | 50 ++++- .../visualizations/charts/bar/bar_type.ts | 7 + .../charts/bar/horizontal_bar_type.ts | 7 + .../charts/bubble/bubble_type.ts | 7 + .../candle_stick/candle_stick_type.ts | 7 + .../charts/financial/gauge/gauge_type.ts | 7 + .../charts/histogram/histogram_type.ts | 7 + .../visualizations/charts/lines/line.tsx | 33 +++- .../visualizations/charts/lines/line_type.ts | 7 + .../charts/maps/heatmap_type.ts | 7 + .../charts/maps/treemap_type.ts | 7 + .../visualizations/charts/pie/pie_type.ts | 7 + 26 files changed, 683 insertions(+), 278 deletions(-) create mode 100644 dashboards-observability/public/components/application_analytics/helpers/types.tsx create mode 100644 dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability.tsx diff --git a/dashboards-observability/common/types/explorer.ts b/dashboards-observability/common/types/explorer.ts index fcf9017d2..d22451d8a 100644 --- a/dashboards-observability/common/types/explorer.ts +++ b/dashboards-observability/common/types/explorer.ts @@ -110,6 +110,9 @@ export interface IExplorerProps { setStartTime?: any; setEndTime?: any; appBaseQuery?: string; + callback?: any; + callbackInApp?: any; + // setAvailability?: boolean; } export interface SavedQuery { diff --git a/dashboards-observability/public/components/application_analytics/components/app_table.tsx b/dashboards-observability/public/components/application_analytics/components/app_table.tsx index 96ce4dd06..1e849b481 100644 --- a/dashboards-observability/public/components/application_analytics/components/app_table.tsx +++ b/dashboards-observability/public/components/application_analytics/components/app_table.tsx @@ -39,6 +39,7 @@ import { getCustomModal } from '../../custom_panels/helpers/modal_containers'; import { getClearModal } from '../helpers/modal_containers'; import { pageStyles, UI_DATE_FORMAT } from '../../../../common/constants/shared'; import { ApplicationListType } from '../../../../common/types/app_analytics'; +import { AvailabilityType } from '../helpers/types'; interface AppTableProps extends AppAnalyticsComponentDeps { loading: boolean; @@ -47,6 +48,7 @@ interface AppTableProps extends AppAnalyticsComponentDeps { renameApplication: (newAppName: string, appId: string) => void; deleteApplication: (appList: string[], panelIdList: string[], toastMessage?: string) => void; clearStorage: () => void; + moveToApp: (id: string, type: string) => void; } export function AppTable(props: AppTableProps) { @@ -59,6 +61,7 @@ export function AppTable(props: AppTableProps) { deleteApplication, setFilters, clearStorage, + moveToApp, } = props; const [isModalVisible, setIsModalVisible] = useState(false); const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); @@ -176,6 +179,22 @@ export function AppTable(props: AppTableProps) { // Add sample application, ]; + const renderAvailability = (value: AvailabilityType, record: ApplicationListType) => { + if (value.color === 'loading') { + return ; + } else if (value.name) { + return {value.name}; + } else if (value.color === 'undefined') { + return No match; + } else { + return ( + moveToApp(record.id, 'createSetAvailability')}> + Set Availability + + ); + } + }; + const tableColumns = [ { field: 'name', @@ -195,10 +214,9 @@ export function AppTable(props: AppTableProps) { truncateText: true, render: (value) => ( - {value.join(', ')} + + {value.join(', ')} + ), }, @@ -206,15 +224,7 @@ export function AppTable(props: AppTableProps) { field: 'availability', name: 'Current Availability', sortable: true, - render: (value, record) => { - if (value.name === 'loading') { - return ; - } else if (value.name) { - return {value.name}; - } else { - return Undefined; - } - }, + render: renderAvailability, }, { field: 'dateModified', diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx index 08ab10f22..328d11323 100644 --- a/dashboards-observability/public/components/application_analytics/components/application.tsx +++ b/dashboards-observability/public/components/application_analytics/components/application.tsx @@ -81,14 +81,6 @@ const searchBarConfigs = { }, }; -export interface DetailTab { - id: string; - label: string; - description: string; - onClick: () => void; - testId: string; -} - interface AppDetailProps extends AppAnalyticsComponentDeps { disabled?: boolean; appId: string; @@ -99,6 +91,7 @@ interface AppDetailProps extends AppAnalyticsComponentDeps { notifications: NotificationsStart; updateApp: (appId: string, updateAppData: Partial, type: string) => void; setToasts: (title: string, color?: string, text?: ReactChild) => void; + callback: (childfunction: () => void) => void; } export function Application(props: AppDetailProps) { @@ -119,6 +112,7 @@ export function Application(props: AppDetailProps) { setAppConfigs, setToasts, setFilters, + callback, } = props; const [application, setApplication] = useState({ name: '', @@ -130,6 +124,7 @@ export function Application(props: AppDetailProps) { availabilityVisId: '', }); const dispatch = useDispatch(); + const [triggerAvailability, setTriggerAvailability] = useState(false); const [selectedTabId, setSelectedTab] = useState(TAB_OVERVIEW_ID); const [serviceFlyoutName, setServiceFlyoutName] = useState(''); const [traceFlyoutId, setTraceFlyoutId] = useState(''); @@ -212,6 +207,7 @@ export function Application(props: AppDetailProps) { ); const tabId = `application-analytics-tab-${appId}`; initializeTabData(dispatch, tabId, NEW_TAB); + callback(switchToEvent); }, [appId]); useEffect(() => { @@ -379,6 +375,9 @@ export function Application(props: AppDetailProps) { setStartTime={setStartTimeForApp} setEndTime={setEndTimeForApp} appBaseQuery={application.baseQuery} + callback={callback} + callbackInApp={callbackInApp} + // setAvailability={triggerSetAvailability} curSelectedTabId={selectedTabId} /> ); @@ -428,13 +427,25 @@ export function Application(props: AppDetailProps) { } }; + const switchToAvailability = () => { + switchToEvent(); + setTriggerAvailability(true); + }; + + const callbackInApp = (childFunc: () => void) => { + if (childFunc && triggerAvailability) { + childFunc(); + setTriggerAvailability(false); + } + }; + const getConfig = () => { return ( diff --git a/dashboards-observability/public/components/application_analytics/components/configuration.tsx b/dashboards-observability/public/components/application_analytics/components/configuration.tsx index c07fc9eab..01aedce74 100644 --- a/dashboards-observability/public/components/application_analytics/components/configuration.tsx +++ b/dashboards-observability/public/components/application_analytics/components/configuration.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiLink, EuiPage, EuiPageBody, EuiPageContent, @@ -25,14 +26,14 @@ import { } from '@elastic/eui'; import { ApplicationType } from 'common/types/app_analytics'; import { last } from 'lodash'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; interface ConfigProps { appId: string; application: ApplicationType; parentBreadcrumbs: EuiBreadcrumb[]; visWithAvailability: EuiSelectOption[]; - switchToEditViz: (savedVizId: string) => void; + switchToAvailability: () => void; updateApp: (appId: string, updateAppData: Partial, type: string) => void; } @@ -43,12 +44,9 @@ export const Configuration = (props: ConfigProps) => { parentBreadcrumbs, visWithAvailability, updateApp, - switchToEditViz, + switchToAvailability, } = props; const [availabilityVisId, setAvailabilityVisId] = useState(application.availabilityVisId || ''); - useEffect(() => { - switchToEditViz(''); - }, []); const onAvailabilityVisChange = (event: any) => { setAvailabilityVisId(event.target.value); @@ -126,11 +124,15 @@ export const Configuration = (props: ConfigProps) => {

Availability

- + {visWithAvailability.length > 0 ? ( + + ) : ( + switchToAvailability()}>Set Availability + )} diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx index 7a209ac1f..f6c8ebe5f 100644 --- a/dashboards-observability/public/components/application_analytics/components/create.tsx +++ b/dashboards-observability/public/components/application_analytics/components/create.tsx @@ -39,7 +39,7 @@ interface CreateAppProps extends AppAnalyticsComponentDeps { dslService: DSLService; pplService: PPLService; setToasts: (title: string, color?: string, text?: ReactChild) => void; - createApp: (app: ApplicationType) => void; + createApp: (app: ApplicationType, type: string) => void; updateApp: (appId: string, updateAppData: Partial, type: string) => void; clearStorage: () => void; existingAppId: string; @@ -138,7 +138,7 @@ export const CreateApp = (props: CreateAppProps) => { } }; - const onCreate = () => { + const onCreate = (type: string) => { const appData = { name, description, @@ -148,7 +148,7 @@ export const CreateApp = (props: CreateAppProps) => { panelId: '', availabilityVisId: '', }; - createApp(appData); + createApp(appData, type); }; const onUpdate = () => { @@ -234,7 +234,21 @@ export const CreateApp = (props: CreateAppProps) => { - + onCreate('createSetAvailability')} + > + Create and Set Availability + + + + + + onCreate('create')} + fill + > {editMode ? 'Save' : 'Create'} diff --git a/dashboards-observability/public/components/application_analytics/helpers/types.tsx b/dashboards-observability/public/components/application_analytics/helpers/types.tsx new file mode 100644 index 000000000..07ac03c9d --- /dev/null +++ b/dashboards-observability/public/components/application_analytics/helpers/types.tsx @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface AvailabilityType { + name: string; + color: string; + mainVisId: string; +} diff --git a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx index 40f8e1f98..eb22d72b7 100644 --- a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx +++ b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx @@ -18,7 +18,10 @@ import { VisualizationType } from '../../../../common/types/custom_panels'; import { NEW_SELECTED_QUERY_TAB, TAB_CREATED_TYPE } from '../../../../common/constants/explorer'; import { APP_ANALYTICS_API_PREFIX } from '../../../../common/constants/application_analytics'; import { HttpSetup } from '../../../../../../src/core/public'; -import { init as initFields, remove as removefields } from '../../event_analytics/redux/slices/field_slice'; +import { + init as initFields, + remove as removefields, +} from '../../event_analytics/redux/slices/field_slice'; import { init as initVisualizationConfig, reset as resetVisualizationConfig, @@ -33,6 +36,7 @@ import { remove as removeQueryResult, } from '../../event_analytics/redux/slices/query_result_slice'; import { addTab, removeTab } from '../../event_analytics/redux/slices/query_tab_slice'; +import { AvailabilityType } from './types'; // Name validation export const isNameValid = (name: string, existingNames: string[]) => { @@ -192,7 +196,7 @@ export const calculateAvailability = async ( application: ApplicationType | ApplicationListType, availabilityVisId: string, setVisWithAvailability: (visList: EuiSelectOption[]) => void -): Promise<{ name: string; color: string; mainVisId: string }> => { +): Promise => { let availability = { name: '', color: '', mainVisId: '' }; const panelId = application.panelId; if (!panelId) return availability; @@ -209,9 +213,9 @@ export const calculateAvailability = async ( const visData = await fetchVisualizationById(http, visualizationId, (value: string) => console.error(value) ); - // If there are thresholds, we get the current value - if (visData.user_configs.dataConfig?.hasOwnProperty('thresholds')) { - const thresholds = visData.user_configs.dataConfig.thresholds.reverse(); + // If there are levels, we get the current value + if (visData.user_configs.availabilityConfig?.hasOwnProperty('level')) { + const levels = visData.user_configs.availabilityConfig.level.reverse(); let currValue = Number.MIN_VALUE; const finalQuery = preprocessQuery({ rawQuery: visData.query, @@ -235,78 +239,75 @@ export const calculateAvailability = async ( .catch((err) => { console.error(err); }); - // We check each threshold if it has expression which means it is an availability level - for (let j = 0; j < thresholds.length; j++) { - const threshold = thresholds[j]; - if (threshold.hasOwnProperty('expression')) { - hasAvailability = true; - // If there is an availiabilityVisId selected we only want to compute availability based on that - if (availabilityVisId ? availabilityVisId === visualizationId : true) { - if (threshold.value !== null) { - if (!availabilityFound && threshold.expression) { - const expression = threshold.expression; - switch (expression) { - case '≥': - if (currValue >= parseFloat(threshold.value)) { - availability = { - name: threshold.name, - color: threshold.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '≤': - if (currValue <= parseFloat(threshold.value)) { - availability = { - name: threshold.name, - color: threshold.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '>': - if (currValue > parseFloat(threshold.value)) { - availability = { - name: threshold.name, - color: threshold.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '<': - if (currValue < parseFloat(threshold.value)) { - availability = { - name: threshold.name, - color: threshold.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '=': - if (currValue === parseFloat(threshold.value)) { - availability = { - name: threshold.name, - color: threshold.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '≠': - if (currValue !== parseFloat(threshold.value)) { - availability = { - name: threshold.name, - color: threshold.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - } + for (let j = 0; j < levels.length; j++) { + const level = levels[j]; + hasAvailability = true; + // 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 (!availabilityFound && level.expression) { + const expression = level.expression; + switch (expression) { + case '≥': + if (currValue >= parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '≤': + if (currValue <= parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '>': + if (currValue > parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '<': + if (currValue < parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '=': + if (currValue === parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '≠': + if (currValue !== parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; } } } @@ -320,5 +321,8 @@ export const calculateAvailability = async ( } } setVisWithAvailability(visWithAvailability); + if (!availabilityFound && visWithAvailability.length > 0) { + return { name: '', color: 'undefined', mainVisId: '' }; + } return availability; }; diff --git a/dashboards-observability/public/components/application_analytics/home.tsx b/dashboards-observability/public/components/application_analytics/home.tsx index d3074da8b..a596d5291 100644 --- a/dashboards-observability/public/components/application_analytics/home.tsx +++ b/dashboards-observability/public/components/application_analytics/home.tsx @@ -67,6 +67,7 @@ export const Home = (props: HomeProps) => { chrome, notifications, } = props; + const [triggerSwitchToEvent, setTriggerSwitchToEvent] = useState(0); const dispatch = useDispatch(); const [applicationList, setApplicationList] = useState([]); const [toasts, setToasts] = useState([]); @@ -145,7 +146,14 @@ export const Home = (props: HomeProps) => { setQueryWithStorage(''); }; - const createPanelForApp = (applicationId: string, appName: string) => { + const moveToApp = (id: string, type: string) => { + window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics/${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({ @@ -154,7 +162,7 @@ export const Home = (props: HomeProps) => { }), }) .then((res) => { - updateApp(applicationId, { panelId: res.newPanelId }, 'addPanel'); + updateApp(applicationId, { panelId: res.newPanelId }, type); }) .catch((err) => { setToast( @@ -203,7 +211,7 @@ export const Home = (props: HomeProps) => { const mainVisIdStore: Record = {}; for (let i = 0; i < res.data.length; i++) { mainVisIdStore[res.data[i].id] = res.data[i].availability.mainVisId; - res.data[i].availability = { name: 'loading', color: '', mainVisId: '' }; + res.data[i].availability = { name: '', color: 'loading', mainVisId: '' }; } setApplicationList(res.data); for (let i = res.data.length - 1; i > -1; i--) { @@ -228,7 +236,7 @@ export const Home = (props: HomeProps) => { }; // Create a new application - const createApp = (application: ApplicationType) => { + const createApp = (application: ApplicationType, type: string) => { const toast = isNameValid( application.name, applicationList.map((obj) => obj.name) @@ -252,7 +260,7 @@ export const Home = (props: HomeProps) => { body: JSON.stringify(requestBody), }) .then(async (res) => { - createPanelForApp(res.newAppId, application.name); + createPanelForApp(res.newAppId, application.name, type); setToast(`Application "${application.name}" successfully created!`); clearStorage(); }) @@ -315,10 +323,8 @@ export const Home = (props: HomeProps) => { setToast('Application successfully updated.'); clearStorage(); } - if (type !== 'editAvailability') { - window.location.assign( - `${last(parentBreadcrumbs)!.href}application_analytics/${res.updatedAppId}` - ); + if (type.startsWith('create')) { + moveToApp(res.updatedAppId, type); } }) .catch((err) => { @@ -355,6 +361,13 @@ export const Home = (props: HomeProps) => { }); }; + const callback = (childFunc: () => void) => { + if (childFunc && triggerSwitchToEvent > 0) { + childFunc(); + setTriggerSwitchToEvent(triggerSwitchToEvent - 1); + } + }; + return (
{ renameApplication={renameApp} deleteApplication={deleteApp} clearStorage={clearStorage} + moveToApp={moveToApp} {...commonProps} /> @@ -412,6 +426,7 @@ export const Home = (props: HomeProps) => { notifications={notifications} setToasts={setToast} updateApp={updateApp} + callback={callback} {...commonProps} /> )} diff --git a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx index 37cd5affd..6890fa262 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ -/* eslint-disable no-console */ import './explorer.scss'; import React, { useState, useMemo, useEffect, useRef, useCallback, ReactElement } from 'react'; @@ -54,7 +53,12 @@ import { FINAL_QUERY, DATE_PICKER_FORMAT, } from '../../../../common/constants/explorer'; -import { PPL_STATS_REGEX, PPL_NEWLINE_REGEX, LIVE_OPTIONS, LIVE_END_TIME } from '../../../../common/constants/shared'; +import { + PPL_STATS_REGEX, + PPL_NEWLINE_REGEX, + LIVE_OPTIONS, + LIVE_END_TIME, +} from '../../../../common/constants/shared'; import { getIndexPatternFromRawQuery, preprocessQuery, buildQuery } from '../../../../common/utils'; import { useFetchEvents, useFetchVisualizations } from '../hooks'; import { changeQuery, changeDateRange, selectQueries } from '../redux/slices/query_slice'; @@ -71,10 +75,7 @@ import { change as updateVizConfig } from '../redux/slices/viualization_config_s import { IExplorerProps, IVisualizationContainerProps } from '../../../../common/types/explorer'; import { TabContext } from '../hooks'; import { getVizContainerProps } from '../../visualizations/charts/helpers'; -import { - parseGetSuggestions, - onItemSelect, -} from '../../common/search/autocomplete_logic'; +import { parseGetSuggestions, onItemSelect } from '../../common/search/autocomplete_logic'; import { formatError } from '../utils'; import { sleep } from '../../common/live_tail/live_tail_button'; @@ -103,6 +104,8 @@ export const Explorer = ({ endTime, setStartTime, setEndTime, + callback, + callbackInApp, }: IExplorerProps) => { const dispatch = useDispatch(); const requestParams = { tabId }; @@ -159,7 +162,7 @@ export const Explorer = ({ const momentStart = dateMath.parse(start)!; const momentEnd = dateMath.parse(end)!; const diffSeconds = momentEnd.unix() - momentStart.unix(); - + // less than 1 second if (diffSeconds <= 1) minInterval = 'ms'; // less than 2 minutes @@ -174,7 +177,7 @@ export const Explorer = ({ else if (diffSeconds <= 86400 * 93) minInterval = 'w'; // less than 1 year else if (diffSeconds <= 86400 * 366) minInterval = 'M'; - + setTimeIntervalOptions([ { text: 'Auto', value: 'auto_' + minInterval }, ...TIME_INTERVAL_OPTIONS, @@ -182,11 +185,10 @@ export const Explorer = ({ }; useEffect(() => { - document.addEventListener("visibilitychange", function() { + document.addEventListener('visibilitychange', function () { if (document.hidden) { setBrowserTabFocus(false); - } - else { + } else { setBrowserTabFocus(true); } }); @@ -224,7 +226,7 @@ export const Explorer = ({ const currQuery = appLogEvents ? objectData?.query.replace(appBaseQuery + '| ', '') : objectData?.query || ''; - + if (appLogEvents) { if (objectData?.selected_date_range?.start && objectData?.selected_date_range?.end) { setStartTime(objectData.selected_date_range.start); @@ -298,7 +300,7 @@ export const Explorer = ({ indexPattern: string ): Promise => await timestampUtils.getTimestamp(indexPattern); - const fetchData = async (startTime?: string, endTime?: string) => { + const fetchData = async (startingTime?: string, endingTime?: string) => { const curQuery = queryRef.current; const rawQueryStr = buildQuery(appBaseQuery, curQuery![RAW_QUERY]); const curIndex = getIndexPatternFromRawQuery(rawQueryStr); @@ -323,16 +325,16 @@ export const Explorer = ({ } } - if ((isEqual(typeof startTime, 'undefined')) && (isEqual(typeof endTime, 'undefined'))) { - startTime = curQuery![SELECTED_DATE_RANGE][0]; - endTime = curQuery![SELECTED_DATE_RANGE][1]; + if (isEqual(typeof startingTime, 'undefined') && isEqual(typeof endingTime, 'undefined')) { + startingTime = curQuery![SELECTED_DATE_RANGE][0]; + endingTime = curQuery![SELECTED_DATE_RANGE][1]; } // compose final query const finalQuery = composeFinalQuery( curQuery, - startTime, - endTime, + startingTime!, + endingTime!, curTimestamp, isLiveTailOnRef.current ); @@ -353,7 +355,7 @@ export const Explorer = ({ getAvailableFields(`search source=${curIndex}`); } else { findAutoInterval(startTime, endTime); - if (isLiveTailOnRef.current){ + if (isLiveTailOnRef.current) { getLiveTail(undefined, (error) => { const formattedError = formatError(error.name, error.message, error.body.message); notifications.toasts.addError(formattedError, { @@ -372,7 +374,7 @@ export const Explorer = ({ } // for comparing usage if for the same tab, user changed index from one to another - if (!isLiveTailOnRef.current){ + if (!isLiveTailOnRef.current) { setPrevIndex(curTimestamp); if (!queryRef.current!.isLoaded) { dispatch( @@ -394,6 +396,31 @@ export const Explorer = ({ await getSavedDataById(objectId); }; + const prepareAvailability = async () => { + setSelectedContentTab(TAB_CHART_ID); + setCurVisId('line'); + setTempQuery('stats count() by span( timestamp, 1h )'); + handleTimeRangePickerRefresh(); + }; + + useEffect(() => { + if (callback) { + callback(() => prepareAvailability()); + } + if (callbackInApp) { + callbackInApp(() => prepareAvailability()); + } + }, [appBaseQuery]); + + // useEffect(() => { + // if (setAvailability) { + // setSelectedContentTab(TAB_CHART_ID); + // setCurVisId('line'); + // setTempQuery('stats count() by span( timestamp, 1h )'); + // handleTimeRangePickerRefresh(); + // } + // }, [setAvailability]); + useEffect(() => { if (queryRef.current!.isLoaded) return; let objectId; @@ -434,6 +461,8 @@ export const Explorer = ({ }) ); await fetchData(); + callback(() => prepareAvailability()); + callbackInApp(() => prepareAvailability()); }; const handleAddField = (field: IField) => toggleFields(field, AVAILABLE_FIELDS, SELECTED_FIELDS); @@ -535,17 +564,19 @@ export const Explorer = ({ }; const totalHits: number = useMemo(() => { - if (isLiveTailOn && countDistribution?.data) { - let hits = reduce( - countDistribution['data']['count()'], - (sum, n) => { - return sum + n; - }, - liveHits - ) - setLiveHits(hits); - return hits - }} , [countDistribution?.data]); + if (isLiveTailOn && countDistribution?.data) { + const hits = reduce( + countDistribution.data['count()'], + (sum, n) => { + return sum + n; + }, + liveHits + ); + setLiveHits(hits); + return hits; + } + return 0; + }, [countDistribution?.data]); const getMainContent = () => { return ( @@ -637,24 +668,24 @@ export const Explorer = ({
{isLiveTailOnRef.current && ( - <> - - - - -   Live streaming - - - { } } /> - - - since {liveTimestamp} - - - + <> + + + + +   Live streaming + + + {}} + /> + + since {liveTimestamp} + + + )} ); }; @@ -1019,21 +1051,21 @@ export const Explorer = ({ const liveTailLoop = async ( name: string, - startTime: string, - endTime: string, + startingTime: string, + endingTime: string, delayTime: number ) => { setLiveTailName(name); - setLiveTailTabId(curSelectedTabId.current); + setLiveTailTabId((curSelectedTabId.current as unknown) as string); setIsLiveTailOn(true); setToast('Live tail On', 'success'); setIsLiveTailPopoverOpen(false); - setLiveTimestamp(dateMath.parse(endTime)?.utc().format(DATE_PICKER_FORMAT)); + setLiveTimestamp(dateMath.parse(endingTime)?.utc().format(DATE_PICKER_FORMAT) || ''); setLiveHits(0); await sleep(2000); const curLiveTailname = liveTailNameRef.current; while (isLiveTailOnRef.current === true && curLiveTailname === liveTailNameRef.current) { - handleLiveTailSearch(startTime, endTime); + handleLiveTailSearch(startingTime, endingTime); if (liveTailTabIdRef.current !== curSelectedTabId.current) { setIsLiveTailOn(false); isLiveTailOnRef.current = false; @@ -1050,36 +1082,36 @@ export const Explorer = ({ setLiveHits(0); setIsLiveTailPopoverOpen(false); if (isLiveTailOnRef.current) setToast('Live tail Off', 'danger'); - } + }; useEffect(() => { - if ((isEqual(selectedContentTabId, TAB_CHART_ID)) || (!browserTabFocus)) { + if (isEqual(selectedContentTabId, TAB_CHART_ID) || !browserTabFocus) { stopLive(); } }, [selectedContentTabId, browserTabFocus]); - //stop live tail if the page is moved using breadcrumbs - let lastUrl = location.href; + // stop live tail if the page is moved using breadcrumbs + let lastUrl = location.href; new MutationObserver(() => { const url = location.href; - if (url !== lastUrl) { - lastUrl = url; - stopLive(); - } - }).observe(document, {subtree: true, childList: true}); + if (url !== lastUrl) { + lastUrl = url; + stopLive(); + } + }).observe(document, { subtree: true, childList: true }); const popoverItems: ReactElement[] = LIVE_OPTIONS.map((e) => { return ( - { - liveTailLoop(e.label, e.startTime, LIVE_END_TIME, e.delayTime); - }} - data-test-subj={'eventLiveTail__delay'+e.label} - > - {e.label} - - ) + { + liveTailLoop(e.label, e.startTime, LIVE_END_TIME, e.delayTime); + }} + data-test-subj={'eventLiveTail__delay' + e.label} + > + {e.label} + + ); }); const dateRange = @@ -1090,9 +1122,9 @@ export const Explorer = ({ : [startTime, endTime]; const handleLiveTailSearch = useCallback( - async (startTime: string, endTime: string) => { + async (startingTime: string, endingTime: string) => { await updateQueryInStore(tempQuery); - fetchData(startTime, endTime); + fetchData(startingTime, endingTime); }, [tempQuery] ); @@ -1153,4 +1185,4 @@ export const Explorer = ({
); -}; \ No newline at end of file +}; diff --git a/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx b/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx index 9a49ca926..c949c9e49 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx @@ -15,6 +15,7 @@ import { Field } from './field'; import { IExplorerFields, IField } from '../../../../../common/types/explorer'; interface ISidebarProps { + query: string; explorerFields: IExplorerFields; explorerData: any; selectedTimestamp: string; diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx index f2eea5902..4f4e2cf8a 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx @@ -17,7 +17,8 @@ import { EuiComboBox, EuiPanel, EuiIcon, - EuiComboBoxOptionOption + EuiComboBoxOptionOption, + EuiTabbedContentTab, } from '@elastic/eui'; import { reset as resetVisualizationConfig } from '../../../redux/slices/viualization_config_slice'; import { getDefaultSpec } from '../visualization_specs/default_spec'; @@ -50,6 +51,15 @@ const HJSON_STRINGIFY_OPTIONS = { bracesSameLine: true, }; +interface PanelTabType { + id: string; + name: string; + mapTo: string; + editor: any; + section?: any; + content?: any; +} + export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { const { tabId, curVisId, dispatch, changeVisualizationConfig, setToast } = useContext( TabContext @@ -62,6 +72,7 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { layoutConfig: userConfigs?.layoutConfig ? hjson.stringify({ ...userConfigs.layoutConfig }, HJSON_STRINGIFY_OPTIONS) : getDefaultSpec(), + availabilityConfig: {}, }); useEffect(() => { @@ -95,13 +106,13 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { }, }) ); - } catch (e) { + } catch (e: any) { setToast(`Invalid visualization configurations. error: ${e.message}`, 'danger'); } }, [tabId, vizConfigs, changeVisualizationConfig, dispatch, setToast, curVisId]); - const handleConfigChange = (configSchema) => { - return (configChanges) => { + const handleConfigChange = (configSchema: string) => { + return (configChanges: any) => { setVizConfigs((staleState) => { return { ...staleState, @@ -111,22 +122,30 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { }; }; - const params = { - dataConfig: { - visualizations, - curVisId, - onConfigChange: handleConfigChange('dataConfig'), - vizState: vizConfigs.dataConfig, - }, - layoutConfig: { - onConfigEditorChange: handleConfigChange('layoutConfig'), - spec: vizConfigs.layoutConfig, - setToast, - }, - }; + const params = useMemo(() => { + return { + dataConfig: { + visualizations, + curVisId, + onConfigChange: handleConfigChange('dataConfig'), + vizState: vizConfigs.dataConfig, + }, + layoutConfig: { + onConfigEditorChange: handleConfigChange('layoutConfig'), + spec: vizConfigs.layoutConfig, + setToast, + }, + availabilityConfig: { + visualizations, + curVisId, + onConfigChange: handleConfigChange('availabilityConfig'), + vizState: vizConfigs.availabilityConfig, + }, + }; + }, [visualizations, vizConfigs, setToast, curVisId]); - const tabs = useMemo(() => { - return vis.editorConfig.panelTabs.map((tab) => { + const tabs: EuiTabbedContentTab[] = useMemo(() => { + return vis.editorConfig.panelTabs.map((tab: PanelTabType) => { const Editor = tab.editor; return { id: tab.id, @@ -136,6 +155,16 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { }); }, [vis.editorConfig.panelTabs, params]); + const [currTabId, setCurrTabId] = useState(tabs[0].id); + + // const switchToAvailability = () => { + // setCurrTabId('availability-panel'); + // }; + + const onTabClick = (selectedTab: EuiTabbedContentTab) => { + setCurrTabId(selectedTab.id); + }; + const handleDiscardConfig = () => { dispatch( resetVisualizationConfig({ @@ -187,9 +216,7 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { gutterSize="none" responsive={false} > - + { tab.id === currTabId) || tabs[0]} + onTabClick={onTabClick} /> diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability.tsx new file mode 100644 index 000000000..b4403d777 --- /dev/null +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability.tsx @@ -0,0 +1,176 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useCallback } from 'react'; +import { + EuiButton, + EuiAccordion, + EuiFormRow, + EuiFieldNumber, + EuiColorPicker, + EuiSpacer, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiSelect, + htmlIdGenerator, +} from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { PPL_SPAN_REGEX } from '../../../../../../../../common/constants/shared'; + +export interface AvailabilityUnitType { + thid: string; + name: string; + color: string; + value: number; + expression: string; +} + +export const ConfigAvailability = ({ visualizations, onConfigChange, vizState = {} }: any) => { + const addButtonText = '+ Add availability level'; + const getAvailabilityUnit = () => { + return { + thid: htmlIdGenerator('avl')(), + name: '', + color: '#FC0505', + value: 0, + expression: '≥', + }; + }; + + const expressionOptions = [ + { value: '≥', text: '≥' }, + { value: '≤', text: '≤' }, + { value: '>', text: '>' }, + { value: '<', text: '<' }, + { value: '=', text: '=' }, + { value: '≠', text: '≠' }, + ]; + + const hasSpanInApp = + visualizations.data.query.finalQuery.search(PPL_SPAN_REGEX) > 0 && + visualizations.data.appData.fromApp && + ['bar', 'line'].includes(visualizations.vis.id); + + const handleConfigChange = useCallback( + (changes: any) => { + onConfigChange({ + ...vizState, + level: changes, + }); + }, + [onConfigChange, vizState] + ); + + const handleAddAvailability = useCallback(() => { + let res = vizState.level; + if (isEmpty(vizState.level)) res = []; + handleConfigChange([getAvailabilityUnit(), ...res]); + }, [vizState, handleConfigChange]); + + const handleAvailabilityChange = useCallback( + (thrId, thrName) => { + return (event: any) => { + handleConfigChange([ + ...vizState.level.map((th: AvailabilityUnitType) => { + if (thrId !== th.thid) return th; + return { + ...th, + [thrName]: (thrName === 'color' ? event : event?.target?.value) || '', + }; + }), + ]); + }; + }, + [vizState, handleConfigChange] + ); + + const handleAvailabilityDelete = useCallback( + (thrId) => { + return (event: any) => { + handleConfigChange([ + ...vizState.level.filter((th: AvailabilityUnitType) => th.thid !== thrId), + ]); + }; + }, + [vizState, handleConfigChange] + ); + + return ( + <> + + + + {addButtonText} + + + {!isEmpty(vizState.level) && + vizState.level.map((thr: AvailabilityUnitType) => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + })} + + + ); +}; diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_thresholds.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_thresholds.tsx index 7033e0080..f1941a506 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_thresholds.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_thresholds.tsx @@ -15,11 +15,16 @@ import { EuiFlexGroup, EuiFlexItem, EuiFieldText, - EuiSelect, htmlIdGenerator, } from '@elastic/eui'; import { isEmpty } from 'lodash'; -import { PPL_SPAN_REGEX } from '../../../../../../../../common/constants/shared'; + +export interface ThresholdUnitType { + thid: string; + name: string; + color: string; + value: number; +} export const ConfigThresholds = ({ visualizations, @@ -28,34 +33,16 @@ export const ConfigThresholds = ({ handleConfigChange, sectionName = 'Thresholds', }: any) => { - let addButtonText = '+ Add threadshold'; + const addButtonText = '+ Add threadshold'; const getThresholdUnit = () => { return { thid: htmlIdGenerator('thr')(), name: '', color: '#FC0505', value: 0, - ...(hasSpanInApp && { expression: '≥' }), }; }; - const expressionOptions = [ - { value: '≥', text: '≥' }, - { value: '≤', text: '≤' }, - { value: '>', text: '>' }, - { value: '<', text: '<' }, - { value: '=', text: '=' }, - { value: '≠', text: '≠' }, - ]; - - const hasSpanInApp = - visualizations.data.query.finalQuery.search(PPL_SPAN_REGEX) > 0 && - visualizations.data.appData.fromApp; - if (hasSpanInApp) { - sectionName = 'Availability Levels'; - addButtonText = '+ Add availability level'; - } - const handleAddThreshold = useCallback(() => { let res = vizState; if (isEmpty(vizState)) res = []; @@ -64,9 +51,9 @@ export const ConfigThresholds = ({ const handleThresholdChange = useCallback( (thrId, thrName) => { - return (event) => { + return (event: any) => { handleConfigChange([ - ...vizState.map((th) => { + ...vizState.map((th: ThresholdUnitType) => { if (thrId !== th.thid) return th; return { ...th, @@ -81,8 +68,8 @@ export const ConfigThresholds = ({ const handleThresholdDelete = useCallback( (thrId) => { - return (event) => { - handleConfigChange([...vizState.filter((th) => th.thid !== thrId)]); + return () => { + handleConfigChange([...vizState.filter((th: ThresholdUnitType) => th.thid !== thrId)]); }; }, [vizState, handleConfigChange] @@ -100,7 +87,7 @@ export const ConfigThresholds = ({ {!isEmpty(vizState) && - vizState.map((thr) => { + vizState.map((thr: ThresholdUnitType) => { return ( <> @@ -124,19 +111,6 @@ export const ConfigThresholds = ({ /> - {hasSpanInApp && ( - - - - - - )} { const { vis } = visualizations; @@ -16,7 +18,11 @@ export const Bar = ({ visualizations, layout, config }: any) => { } = visualizations.data.rawVizData; const { isUniColor } = vis.visConfig; const lastIndex = fields.length - 1; - const { dataConfig = {}, layoutConfig = {} } = visualizations?.data?.userConfigs; + const { + dataConfig = {}, + layoutConfig = {}, + availabilityConfig = {}, + } = visualizations?.data?.userConfigs; const xaxis = dataConfig.valueOptions && dataConfig.valueOptions.xaxis ? dataConfig.valueOptions.xaxis : []; const yaxis = @@ -51,7 +57,7 @@ export const Bar = ({ visualizations, layout, config }: any) => { } // determine category axis - const bars = valueSeries.map((field: any) => { + let bars = valueSeries.map((field: any) => { return { x: isVertical ? data[!isEmpty(xaxis) ? xaxis[0].label : fields[lastIndex].name] @@ -84,6 +90,44 @@ export const Bar = ({ visualizations, layout, config }: any) => { : '', }; + if (dataConfig.thresholds || availabilityConfig.level) { + const thresholdTraces = { + x: [], + y: [], + mode: 'text', + text: [], + }; + const thresholds = dataConfig.thresholds ? dataConfig.thresholds : []; + const levels = availabilityConfig.level ? availabilityConfig.level : []; + + const mapToLine = (list: ThresholdUnitType[] | AvailabilityUnitType[], lineStyle: any) => { + return list.map((thr: ThresholdUnitType) => { + thresholdTraces.x.push( + data[!isEmpty(xaxis) ? xaxis[xaxis.length - 1]?.label : fields[lastIndex].name][0] + ); + thresholdTraces.y.push(thr.value * (1 + 0.06)); + thresholdTraces.text.push(thr.name); + return { + type: 'line', + x0: data[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name][0], + y0: thr.value, + x1: last(data[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name]), + y1: thr.value, + name: thr.name || '', + opacity: 0.7, + line: { + color: thr.color, + width: 3, + ...lineStyle, + }, + }; + }); + }; + + mergedLayout.shapes = [...mapToLine(thresholds, { dash: 'dashdot' }), ...mapToLine(levels, {})]; + bars = [...bars, thresholdTraces]; + } + const mergedConfigs = { ...config, ...(layoutConfig.config && layoutConfig.config), diff --git a/dashboards-observability/public/components/visualizations/charts/bar/bar_type.ts b/dashboards-observability/public/components/visualizations/charts/bar/bar_type.ts index 9e851d48b..fb89427bc 100644 --- a/dashboards-observability/public/components/visualizations/charts/bar/bar_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/bar/bar_type.ts @@ -9,6 +9,7 @@ import { LensIconChartBar } from '../../assets/chart_bar'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -100,6 +101,12 @@ export const createBarTypeDefinition = (params: any) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/bar/horizontal_bar_type.ts b/dashboards-observability/public/components/visualizations/charts/bar/horizontal_bar_type.ts index cbc25a1dc..0401156dd 100644 --- a/dashboards-observability/public/components/visualizations/charts/bar/horizontal_bar_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/bar/horizontal_bar_type.ts @@ -4,6 +4,7 @@ import { LensIconChartBar } from '../../assets/chart_bar'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -97,6 +98,12 @@ export const createHorizontalBarTypeDefinition = (params: BarTypeParams = {}) => editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/bubble/bubble_type.ts b/dashboards-observability/public/components/visualizations/charts/bubble/bubble_type.ts index 091298ce5..165ad53db 100644 --- a/dashboards-observability/public/components/visualizations/charts/bubble/bubble_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/bubble/bubble_type.ts @@ -9,6 +9,7 @@ import { LensIconChartPie } from '../../assets/chart_pie'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -60,6 +61,12 @@ export const createBubbleVisDefinition = () => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, icon: LensIconChartPie, diff --git a/dashboards-observability/public/components/visualizations/charts/financial/candle_stick/candle_stick_type.ts b/dashboards-observability/public/components/visualizations/charts/financial/candle_stick/candle_stick_type.ts index df9dd2fa9..2a4643164 100644 --- a/dashboards-observability/public/components/visualizations/charts/financial/candle_stick/candle_stick_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/financial/candle_stick/candle_stick_type.ts @@ -9,6 +9,7 @@ import { LensIconChartBar } from '../../../assets/chart_bar'; import { VizDataPanel } from '../../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -67,6 +68,12 @@ export const createCandleStickDefinition = (params: BarTypeParams = {}) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/financial/gauge/gauge_type.ts b/dashboards-observability/public/components/visualizations/charts/financial/gauge/gauge_type.ts index e5e91b7db..9840cc325 100644 --- a/dashboards-observability/public/components/visualizations/charts/financial/gauge/gauge_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/financial/gauge/gauge_type.ts @@ -13,6 +13,7 @@ import { ConfigThresholds, ConfigGaugeValueOptions, } from '../../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -77,6 +78,12 @@ export const createGaugeTypeDefinition = (params: any = {}) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/histogram/histogram_type.ts b/dashboards-observability/public/components/visualizations/charts/histogram/histogram_type.ts index 3e6e0d967..9256c56a2 100644 --- a/dashboards-observability/public/components/visualizations/charts/histogram/histogram_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/histogram/histogram_type.ts @@ -9,6 +9,7 @@ import { LensIconChartLine } from '../../assets/chart_line'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -62,6 +63,12 @@ export const createHistogramVisDefinition = (params = {}) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/lines/line.tsx b/dashboards-observability/public/components/visualizations/charts/lines/line.tsx index dfa25e368..4ce620efd 100644 --- a/dashboards-observability/public/components/visualizations/charts/lines/line.tsx +++ b/dashboards-observability/public/components/visualizations/charts/lines/line.tsx @@ -6,6 +6,8 @@ import React, { useMemo } from 'react'; import { take, isEmpty, last } from 'lodash'; import { Plt } from '../../plotly/plot'; +import { AvailabilityUnitType } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; +import { ThresholdUnitType } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_thresholds'; export const Line = ({ visualizations, layout, config }: any) => { const { @@ -13,7 +15,11 @@ export const Line = ({ visualizations, layout, config }: any) => { metadata: { fields }, } = visualizations.data.rawVizData; const { defaultAxes } = visualizations.data; - const { dataConfig = {}, layoutConfig = {} } = visualizations?.data?.userConfigs; + const { + dataConfig = {}, + layoutConfig = {}, + availabilityConfig = {}, + } = visualizations?.data?.userConfigs; const xaxis = dataConfig?.valueOptions && dataConfig.valueOptions.xaxis ? dataConfig.valueOptions.xaxis : []; const yaxis = @@ -30,9 +36,8 @@ export const Line = ({ visualizations, layout, config }: any) => { } else { valueSeries = defaultAxes.yaxis || take(fields, lastIndex > 0 ? lastIndex : 1); } - + const [calculatedLayout, lineValues] = useMemo(() => { - let calculatedLineValues = valueSeries.map((field: any) => { return { x: data[!isEmpty(xaxis) ? xaxis[0]?.label : fields[lastIndex].name], @@ -42,22 +47,25 @@ export const Line = ({ visualizations, layout, config }: any) => { mode, }; }); - + const mergedLayout = { ...layout, ...layoutConfig.layout, title: dataConfig?.panelOptions?.title || layoutConfig.layout?.title || '', }; - if (dataConfig.thresholds) { + if (dataConfig.thresholds || availabilityConfig.level) { const thresholdTraces = { x: [], y: [], mode: 'text', text: [], }; - mergedLayout.shapes = [ - ...dataConfig.thresholds.map((thr) => { + const thresholds = dataConfig.thresholds ? dataConfig.thresholds : []; + const levels = availabilityConfig.level ? availabilityConfig.level : []; + + const mapToLine = (list: ThresholdUnitType[] | AvailabilityUnitType[], lineStyle: any) => { + return list.map((thr: ThresholdUnitType) => { thresholdTraces.x.push( data[!isEmpty(xaxis) ? xaxis[xaxis.length - 1]?.label : fields[lastIndex].name][0] ); @@ -73,11 +81,16 @@ export const Line = ({ visualizations, layout, config }: any) => { opacity: 0.7, line: { color: thr.color, - width: 4, - dash: 'dashdot', + width: 3, + ...lineStyle, }, }; - }), + }); + }; + + mergedLayout.shapes = [ + ...mapToLine(thresholds, { dash: 'dashdot' }), + ...mapToLine(levels, {}), ]; calculatedLineValues = [...calculatedLineValues, thresholdTraces]; } diff --git a/dashboards-observability/public/components/visualizations/charts/lines/line_type.ts b/dashboards-observability/public/components/visualizations/charts/lines/line_type.ts index 68e0df75e..20e452388 100644 --- a/dashboards-observability/public/components/visualizations/charts/lines/line_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/lines/line_type.ts @@ -13,6 +13,7 @@ import { ConfigValueOptions, ConfigThresholds, } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -98,6 +99,12 @@ export const createLineTypeDefinition = (params: any = {}) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/maps/heatmap_type.ts b/dashboards-observability/public/components/visualizations/charts/maps/heatmap_type.ts index 269e429c2..e753715ba 100644 --- a/dashboards-observability/public/components/visualizations/charts/maps/heatmap_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/maps/heatmap_type.ts @@ -9,6 +9,7 @@ import { LensIconChartPie } from '../../assets/chart_pie'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -56,6 +57,12 @@ export const createMapsVisDefinition = () => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts b/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts index 74330f342..4c98113bb 100644 --- a/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts @@ -9,6 +9,7 @@ import { LensIconChartBar } from '../../assets/chart_bar'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -67,6 +68,12 @@ export const createTreeMapDefinition = (params: BarTypeParams = {}) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { diff --git a/dashboards-observability/public/components/visualizations/charts/pie/pie_type.ts b/dashboards-observability/public/components/visualizations/charts/pie/pie_type.ts index 7dbc507aa..2f89b8ec2 100644 --- a/dashboards-observability/public/components/visualizations/charts/pie/pie_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/pie/pie_type.ts @@ -10,6 +10,7 @@ import { PLOTLY_COLOR } from '../../../../../common/constants/shared'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -88,6 +89,12 @@ export const createPieTypeDefinition = (params: any) => ({ editor: ConfigEditor, content: [], }, + { + id: 'availability-panel', + name: 'Availability', + mapTo: 'availabilityConfig', + editor: ConfigAvailability, + }, ], }, visConfig: { From 92ec3290b7909062e79b143db36d8e58df65f371 Mon Sep 17 00:00:00 2001 From: Eugene Lee Date: Fri, 29 Apr 2022 13:59:49 -0700 Subject: [PATCH 02/14] Add null value handling on app table Signed-off-by: Eugene Lee --- .../components/app_table.tsx | 2 + .../application_analytics/helpers/utils.tsx | 145 +++++++++--------- 2 files changed, 77 insertions(+), 70 deletions(-) diff --git a/dashboards-observability/public/components/application_analytics/components/app_table.tsx b/dashboards-observability/public/components/application_analytics/components/app_table.tsx index 1e849b481..46431d366 100644 --- a/dashboards-observability/public/components/application_analytics/components/app_table.tsx +++ b/dashboards-observability/public/components/application_analytics/components/app_table.tsx @@ -186,6 +186,8 @@ export function AppTable(props: AppTableProps) { return {value.name}; } else if (value.color === 'undefined') { return No match; + } else if (value.color === 'null') { + return Current value is null; } else { return ( moveToApp(record.id, 'createSetAvailability')}> diff --git a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx index eb22d72b7..43a817dca 100644 --- a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx +++ b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx @@ -207,7 +207,6 @@ export const calculateAvailability = async ( const visWithAvailability = []; let availabilityFound = false; for (let i = 0; i < savedVisualizationsIds.length; i++) { - let hasAvailability = false; const visualizationId = savedVisualizationsIds[i]; // Fetches data for visualization const visData = await fetchVisualizationById(http, visualizationId, (value: string) => @@ -215,6 +214,10 @@ export const calculateAvailability = async ( ); // If there are levels, we get the current value if (visData.user_configs.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 = visData.user_configs.availabilityConfig.level.reverse(); let currValue = Number.MIN_VALUE; const finalQuery = preprocessQuery({ @@ -241,84 +244,86 @@ export const calculateAvailability = async ( }); for (let j = 0; j < levels.length; j++) { const level = levels[j]; - hasAvailability = true; // 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 (!availabilityFound && level.expression) { - const expression = level.expression; - switch (expression) { - case '≥': - if (currValue >= parseFloat(level.value)) { - availability = { - name: level.name, - color: level.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '≤': - if (currValue <= parseFloat(level.value)) { - availability = { - name: level.name, - color: level.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '>': - if (currValue > parseFloat(level.value)) { - availability = { - name: level.name, - color: level.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '<': - if (currValue < parseFloat(level.value)) { - availability = { - name: level.name, - color: level.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '=': - if (currValue === parseFloat(level.value)) { - availability = { - name: level.name, - color: level.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; - case '≠': - if (currValue !== parseFloat(level.value)) { - availability = { - name: level.name, - color: level.color, - mainVisId: visualizationId, - }; - availabilityFound = true; - } - break; + if (currValue === null) { + availability = { + name: '', + color: 'null', + mainVisId: '', + }; + } else { + if (!availabilityFound) { + const expression = level.expression; + switch (expression) { + case '≥': + if (currValue >= parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '≤': + if (currValue <= parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '>': + if (currValue > parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '<': + if (currValue < parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '=': + if (currValue === parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + case '≠': + if (currValue !== parseFloat(level.value)) { + availability = { + name: level.name, + color: level.color, + mainVisId: visualizationId, + }; + availabilityFound = true; + } + break; + } } } } } } } - // For every saved visualization with availability levels we push it to visWithAvailability - if (hasAvailability) { - // This is used to populate the options in configuration - visWithAvailability.push({ value: visualizationId, text: visData.name }); - } } setVisWithAvailability(visWithAvailability); if (!availabilityFound && visWithAvailability.length > 0) { From 794fdd9fe53ef10646c8d96c34c67c32ec6a9e7d Mon Sep 17 00:00:00 2001 From: Eugene Lee Date: Thu, 5 May 2022 10:57:21 -0700 Subject: [PATCH 03/14] Fixed no valid index bug Signed-off-by: Eugene Lee --- .../common/constants/explorer.ts | 1 + .../common/types/explorer.ts | 1 - .../components/common/search/date_picker.tsx | 2 +- .../components/common/search/search.tsx | 1 + .../event_analytics/explorer/explorer.tsx | 94 +++++++------------ 5 files changed, 39 insertions(+), 60 deletions(-) diff --git a/dashboards-observability/common/constants/explorer.ts b/dashboards-observability/common/constants/explorer.ts index df666d9d5..fbe09f13b 100644 --- a/dashboards-observability/common/constants/explorer.ts +++ b/dashboards-observability/common/constants/explorer.ts @@ -76,3 +76,4 @@ export const REDUX_EXPL_SLICE_VISUALIZATION = 'explorerVisualization'; export const REDUX_EXPL_SLICE_COUNT_DISTRIBUTION = 'countDistributionVisualization'; export const PLOTLY_GAUGE_COLUMN_NUMBER = 5; export const APP_ANALYTICS_TAB_ID_REGEX = /application-analytics-tab.+/; +export const DEFAULT_AVAILABILITY_QUERY = 'stats count() by span( timestamp, 1h )'; diff --git a/dashboards-observability/common/types/explorer.ts b/dashboards-observability/common/types/explorer.ts index d22451d8a..d43aba751 100644 --- a/dashboards-observability/common/types/explorer.ts +++ b/dashboards-observability/common/types/explorer.ts @@ -112,7 +112,6 @@ export interface IExplorerProps { appBaseQuery?: string; callback?: any; callbackInApp?: any; - // setAvailability?: boolean; } export interface SavedQuery { diff --git a/dashboards-observability/public/components/common/search/date_picker.tsx b/dashboards-observability/public/components/common/search/date_picker.tsx index 2d3ec8b19..75087c9f1 100644 --- a/dashboards-observability/public/components/common/search/date_picker.tsx +++ b/dashboards-observability/public/components/common/search/date_picker.tsx @@ -11,7 +11,7 @@ import { uiSettingsService } from '../../../../common/utils'; export function DatePicker(props: IDatePickerProps) { const { startTime, endTime, handleTimePickerChange, handleTimeRangePickerRefresh } = props; - const handleTimeChange = (e) => handleTimePickerChange([e.start, e.end]); + const handleTimeChange = (e: any) => handleTimePickerChange([e.start, e.end]); return ( void; setIsOutputStale: () => void; handleTimePickerChange: (timeRange: string[]) => any; + handleTimeRangePickerRefresh: () => any; } export const Search = (props: any) => { diff --git a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx index e5c69d10a..c914991cd 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx @@ -49,8 +49,7 @@ import { EVENT_ANALYTICS_DOCUMENTATION_URL, TAB_EVENT_ID, TAB_CHART_ID, - INDEX, - FINAL_QUERY, + DEFAULT_AVAILABILITY_QUERY, DATE_PICKER_FORMAT, } from '../../../../common/constants/explorer'; import { @@ -143,6 +142,8 @@ export const Explorer = ({ const [liveTimestamp, setLiveTimestamp] = useState(DATE_PICKER_FORMAT); const queryRef = useRef(); + const appBasedRef = useRef(''); + appBasedRef.current = appBaseQuery; const selectedPanelNameRef = useRef(''); const explorerFieldsRef = useRef(); const isLiveTailOnRef = useRef(false); @@ -201,7 +202,7 @@ export const Explorer = ({ timeField: string, isLiveQuery: boolean ) => { - const fullQuery = buildQuery(appBaseQuery, curQuery![RAW_QUERY]); + const fullQuery = buildQuery(appBasedRef.current, curQuery![RAW_QUERY]); if (isEmpty(fullQuery)) return ''; return preprocessQuery({ rawQuery: fullQuery, @@ -302,7 +303,7 @@ export const Explorer = ({ const fetchData = async (startingTime?: string, endingTime?: string) => { const curQuery = queryRef.current; - const rawQueryStr = buildQuery(appBaseQuery, curQuery![RAW_QUERY]); + const rawQueryStr = buildQuery(appBasedRef.current, curQuery![RAW_QUERY]); const curIndex = getIndexPatternFromRawQuery(rawQueryStr); if (isEmpty(rawQueryStr)) return; @@ -350,7 +351,7 @@ export const Explorer = ({ ); // search - if (rawQueryStr.match(PPL_STATS_REGEX)) { + if (finalQuery.match(PPL_STATS_REGEX)) { getVisualizations(); getAvailableFields(`search source=${curIndex}`); } else { @@ -398,28 +399,21 @@ export const Explorer = ({ const prepareAvailability = async () => { setSelectedContentTab(TAB_CHART_ID); - setCurVisId('line'); - setTempQuery('stats count() by span( timestamp, 1h )'); - handleTimeRangePickerRefresh(); + await setTempQuery(DEFAULT_AVAILABILITY_QUERY); + await updateQueryInStore(DEFAULT_AVAILABILITY_QUERY); + await handleTimeRangePickerRefresh(true); }; useEffect(() => { - if (callback) { - callback(() => prepareAvailability()); - } - if (callbackInApp) { - callbackInApp(() => prepareAvailability()); + if (!isEmpty(appBasedRef.current)) { + if (callback) { + callback(() => prepareAvailability()); + } + if (callbackInApp) { + callbackInApp(() => prepareAvailability()); + } } - }, [appBaseQuery]); - - // useEffect(() => { - // if (setAvailability) { - // setSelectedContentTab(TAB_CHART_ID); - // setCurVisId('line'); - // setTempQuery('stats count() by span( timestamp, 1h )'); - // handleTimeRangePickerRefresh(); - // } - // }, [setAvailability]); + }, [appBasedRef.current]); useEffect(() => { if (queryRef.current!.isLoaded) return; @@ -440,31 +434,10 @@ export const Explorer = ({ if (appLogEvents) { if (savedObjectId) { updateTabData(savedObjectId); - } else { - setTempQuery(''); - emptyTab(); } } }, [savedObjectId]); - const emptyTab = async () => { - await dispatch( - changeQuery({ - tabId, - query: { - [RAW_QUERY]: '', - [FINAL_QUERY]: '', - [INDEX]: '', - [SELECTED_TIMESTAMP]: '', - [SAVED_OBJECT_ID]: '', - }, - }) - ); - await fetchData(); - callback(() => prepareAvailability()); - callbackInApp(() => prepareAvailability()); - }; - const handleAddField = (field: IField) => toggleFields(field, AVAILABLE_FIELDS, SELECTED_FIELDS); const handleRemoveField = (field: IField) => @@ -496,8 +469,8 @@ export const Explorer = ({ ); }; - const handleTimeRangePickerRefresh = () => { - handleQuerySearch(); + const handleTimeRangePickerRefresh = (availability?: boolean) => { + handleQuerySearch(availability); }; /** @@ -818,18 +791,23 @@ export const Explorer = ({ ); }; - const handleQuerySearch = useCallback(async () => { - // clear previous selected timestamp when index pattern changes - if ( - !isEmpty(tempQuery) && - !isEmpty(query[RAW_QUERY]) && - isIndexPatternChanged(tempQuery, query[RAW_QUERY]) - ) { - await updateCurrentTimeStamp(''); - } - await updateQueryInStore(tempQuery); - fetchData(); - }, [tempQuery, query[RAW_QUERY]]); + const handleQuerySearch = useCallback( + async (availability?: boolean) => { + // clear previous selected timestamp when index pattern changes + if ( + !isEmpty(tempQuery) && + !isEmpty(query[RAW_QUERY]) && + isIndexPatternChanged(tempQuery, query[RAW_QUERY]) + ) { + await updateCurrentTimeStamp(''); + } + if (availability !== true) { + await updateQueryInStore(tempQuery); + } + fetchData(); + }, + [tempQuery, query[RAW_QUERY]] + ); const handleQueryChange = async (newQuery: string) => { setTempQuery(newQuery); From 23c0cecbd38388c9a14dd27667404180e1df5db6 Mon Sep 17 00:00:00 2001 From: Eugene Lee Date: Thu, 5 May 2022 11:15:53 -0700 Subject: [PATCH 04/14] Switch to availability tab Signed-off-by: Eugene Lee --- .../components/event_analytics/explorer/explorer.tsx | 10 ++++++++++ .../visualizations/config_panel/config_panel.tsx | 11 +++++++---- .../event_analytics/explorer/visualizations/index.tsx | 3 +++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx index c914991cd..a7fd42262 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx @@ -140,6 +140,7 @@ export const Explorer = ({ const [liveHits, setLiveHits] = useState(0); const [browserTabFocus, setBrowserTabFocus] = useState(true); const [liveTimestamp, setLiveTimestamp] = useState(DATE_PICKER_FORMAT); + const [triggerAvailability, setTriggerAvailability] = useState(false); const queryRef = useRef(); const appBasedRef = useRef(''); @@ -399,6 +400,7 @@ export const Explorer = ({ const prepareAvailability = async () => { setSelectedContentTab(TAB_CHART_ID); + setTriggerAvailability(true); await setTempQuery(DEFAULT_AVAILABILITY_QUERY); await updateQueryInStore(DEFAULT_AVAILABILITY_QUERY); await handleTimeRangePickerRefresh(true); @@ -718,6 +720,13 @@ export const Explorer = ({ }); }, [curVisId, explorerVisualizations, explorerFields, query, userVizConfigs]); + const callbackForConfig = (childFunc: () => void) => { + if (childFunc && triggerAvailability) { + childFunc(); + setTriggerAvailability(false); + } + }; + const getExplorerVis = () => { return ( ); }; diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx index 4f4e2cf8a..9c5b180bf 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx @@ -60,7 +60,7 @@ interface PanelTabType { content?: any; } -export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { +export const ConfigPanel = ({ visualizations, setCurVisId, callback }: any) => { const { tabId, curVisId, dispatch, changeVisualizationConfig, setToast } = useContext( TabContext ); @@ -82,6 +82,9 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { ? hjson.stringify({ ...userConfigs.layoutConfig }, HJSON_STRINGIFY_OPTIONS) : getDefaultSpec(), }); + if (callback) { + callback(() => switchToAvailability()); + } }, [userConfigs, curVisId]); const getParsedLayoutConfig = useCallback( @@ -157,9 +160,9 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { const [currTabId, setCurrTabId] = useState(tabs[0].id); - // const switchToAvailability = () => { - // setCurrTabId('availability-panel'); - // }; + const switchToAvailability = () => { + setCurrTabId('availability-panel'); + }; const onTabClick = (selectedTab: EuiTabbedContentTab) => { setCurrTabId(selectedTab.id); diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx index 342a3c1ef..16132957a 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/index.tsx @@ -26,6 +26,7 @@ interface IExplorerVisualizationsProps { handleRemoveField: (field: IField) => void; visualizations: IVisualizationContainerProps; handleOverrideTimestamp: (field: IField) => void; + callback?: any; } export const ExplorerVisualizations = ({ @@ -39,6 +40,7 @@ export const ExplorerVisualizations = ({ handleRemoveField, visualizations, handleOverrideTimestamp, + callback, }: IExplorerVisualizationsProps) => { return ( @@ -73,6 +75,7 @@ export const ExplorerVisualizations = ({ visualizations={visualizations} curVisId={curVisId} setCurVisId={setCurVisId} + callback={callback} /> From 03dfef37b1a272d461202171540a4b122b0840d3 Mon Sep 17 00:00:00 2001 From: Eugene Lee Date: Thu, 5 May 2022 14:28:41 -0700 Subject: [PATCH 05/14] Add missing field text for create and set availability: Signed-off-by: Eugene Lee --- .../integration/app_analytics.spec.js | 106 ++++++++---------- .../.cypress/utils/app_constants.js | 9 +- .../components/application.tsx | 1 - .../components/create.tsx | 15 ++- 4 files changed, 59 insertions(+), 72 deletions(-) diff --git a/dashboards-observability/.cypress/integration/app_analytics.spec.js b/dashboards-observability/.cypress/integration/app_analytics.spec.js index c98096fcf..ce7de541c 100644 --- a/dashboards-observability/.cypress/integration/app_analytics.spec.js +++ b/dashboards-observability/.cypress/integration/app_analytics.spec.js @@ -28,7 +28,8 @@ import { visTwoName, composition, newName, - TYPING_DELAY + TYPING_DELAY, + timeoutDelay } from '../utils/app_constants'; import { supressResizeObserverIssue } from '../utils/constants'; @@ -112,21 +113,6 @@ describe('Creating application', () => { cy.get('[data-test-subj="addFirstVisualizationText"]').should('exist'); }); - it('Hides application panels in Operational Panels', () => { - cy.visit( - `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` - ); - cy.get('body').then(($body) => { - if ($body.find('[data-test-subj="customPanels__emptyCreateNewPanels"]').length == 1) { - cy.get('[data-test-subj="operationalPanelSearchBar"]').type(`${nameOne}'s Panel`, {delay: TYPING_DELAY}); - cy.wait(delay); - cy.get('.euiTableCellContent__text').contains('No items found').should('exist'); - cy.get('.euiFormControlLayoutClearButton').click(); - cy.wait(delay); - } - }); - }); - it('Redirects to home page on cancel', () => { cy.get('[data-test-subj="cancelCreateButton"]').contains('Cancel').click(); cy.get('[data-test-subj="applicationHomePageTitle"]').should('exist'); @@ -185,10 +171,35 @@ describe('Creating application', () => { cy.get('.euiButton--danger').contains('Clear all').click(); cy.get('[data-test-subj="traceGroupsCountBadge"]').should('contain', '0'); }); + + it('Saves time range for each application', () => { + cy.get('[data-test-subj="nameFormRow"]').type(nameTwo); + cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); + cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); + cy.get('[data-test-subj="createButton"]').click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); + changeTimeTo24('weeks'); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.wait(delay); + cy.get(`[data-test-subj="${nameOne}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameOne); + changeTimeTo24('months'); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get(`[data-test-subj="${nameTwo}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get(`[data-test-subj="${nameOne}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameOne); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); + }); }); describe('Viewing application', () => { - before(() => { + beforeEach(() => { moveToApplication(nameOne); }); @@ -202,7 +213,6 @@ describe('Viewing application', () => { }); it('Shares time range among tabs', () => { - moveToApplication(nameOne); changeTimeTo24('months'); cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); cy.get('[data-test-subj="app-analytics-serviceTab"]').click(); @@ -217,36 +227,13 @@ describe('Viewing application', () => { }); it('Shows latency variance in dashboards table', () => { - moveToApplication(nameOne); changeTimeTo24('months'); cy.get('[data-test-subj="dashboardTable"]').first().within(($table) => { cy.get('.plot-container').should('have.length.at.least', 1); }) }); - it('Saves time range for each application', () => { - moveToCreatePage(); - cy.get('[data-test-subj="nameFormRow"]').type(nameTwo); - cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); - cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); - cy.get('[data-test-subj="createButton"]').click(); - cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); - changeTimeTo24('weeks'); - cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); - cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); - cy.wait(delay); - cy.get('.euiLink').contains(nameOne).click(); - cy.get('[data-test-subj="applicationTitle"]').should('contain', nameOne); - cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); - cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); - cy.get('.euiLink').contains(nameTwo).click(); - cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); - cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); - }); - it('Adds filter when Trace group name is clicked', () => { - moveToApplication(nameOne); cy.get('[data-test-subj="app-analytics-overviewTab"]').click(); cy.get('.euiLink').contains('client_create_order').click(); cy.get('.euiTableRow').should('have.length', 1); @@ -378,14 +365,9 @@ describe('Viewing application', () => { }); it('Saves visualization #2 to panel with availability level', () => { - moveToApplication(nameOne); changeTimeTo24('months'); cy.get('[data-test-subj="app-analytics-logTab"]').click(); - cy.wait(delay); - cy.get('[id="explorerPlotComponent"]').should('exist'); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').clear(); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type('x', {delay: TYPING_DELAY}); - cy.focused().clear(); + cy.get('[id="explorerPlotComponent"]', { timeout: timeoutDelay }).should('exist'); cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(query_two, {delay: TYPING_DELAY}); cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); cy.wait(delay); @@ -426,8 +408,6 @@ describe('Viewing application', () => { }); it('Configuration tab shows details', () => { - cy.get(`[data-test-subj="${nameOne}ApplicationLink"]`).click(); - cy.wait(delay); cy.get('[data-test-subj="app-analytics-configTab"]').click(); cy.wait(delay); cy.get('[data-test-subj="configBaseQueryCode"]').should('contain', baseQuery); @@ -449,7 +429,9 @@ describe('Viewing application', () => { cy.wait(delay); cy.get('select').find('option:selected').should('have.text', visOneName); }) +}); +describe('Separate from other plugins', () => { it('Hides application visualizations in Event Analytics', () => { cy.visit(`${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/event_analytics`); cy.wait(delay * 3); @@ -485,31 +467,33 @@ describe('Viewing application', () => { cy.visit( `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` ); - cy.get('[data-test-subj="operationalPanelsActionsButton"]').click(); - cy.wait(delay); - cy.get('[data-test-subj="addSampleContextMenuItem"]').click(); - cy.wait(delay); - cy.get('[data-test-subj="confirmModalConfirmButton"]').click(); - cy.wait(delay * 2); + cy.get('[data-test-subj="operationalPanelsActionsButton"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="addSampleContextMenuItem"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="confirmModalConfirmButton"]', { timeout: timeoutDelay }).click(); cy.get('.euiLink').contains('[Logs] Web traffic Panel').first().click(); cy.wait(delay); cy.get('[data-test-subj="addVisualizationButton"]').click(); cy.get('.euiContextMenuItem__text').contains('Select existing visualization').click(); cy.get('option').contains(visOneName).should('not.exist'); cy.get('option').contains(visTwoName).should('not.exist'); + }); + + it('Hides application panels in Operational Panels', () => { cy.visit( `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` ); + cy.get('[data-test-subj="operationalPanelSearchBar"]', { timeout: timeoutDelay }).type(`${nameOne}'s Panel`, {delay: TYPING_DELAY}); + cy.get('.euiTableCellContent__text').contains('No items found').should('exist'); + cy.get('.euiFormControlLayoutClearButton').click(); cy.get('[data-test-subj="operationalPanelSearchBar"]').type('[Logs] Web traffic Panel', {delay: TYPING_DELAY}); - cy.wait(delay); cy.get('.euiTableRow').first().within(($row) => { cy.get('.euiCheckbox').click(); }); - cy.get('[data-test-subj="operationalPanelsActionsButton"]').click(); - cy.get('[data-test-subj="deleteContextMenuItem"]').click(); - cy.get('[data-test-subj="popoverModal__deleteTextInput"]').type('delete'); - cy.get('[data-test-subj="popoverModal__deleteButton"]').should('not.be.disabled'); - cy.get('[data-test-subj="popoverModal__deleteButton"]').click(); + cy.get('[data-test-subj="operationalPanelsActionsButton"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="deleteContextMenuItem"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="popoverModal__deleteTextInput"]', { timeout: timeoutDelay }).type('delete'); + cy.get('[data-test-subj="popoverModal__deleteButton"]', { timeout: timeoutDelay }).should('not.be.disabled'); + cy.get('[data-test-subj="popoverModal__deleteButton"]', { timeout: timeoutDelay }).click(); }); }); diff --git a/dashboards-observability/.cypress/utils/app_constants.js b/dashboards-observability/.cypress/utils/app_constants.js index a36a6fb50..a9ffc9863 100644 --- a/dashboards-observability/.cypress/utils/app_constants.js +++ b/dashboards-observability/.cypress/utils/app_constants.js @@ -6,7 +6,7 @@ import { supressResizeObserverIssue } from './constants'; export const delay = 1000; - +export const timeoutDelay = 30000; export const TYPING_DELAY = 500; export const moveToHomePage = () => { @@ -70,9 +70,10 @@ export const deleteAllSavedApplications = () => { cy.get('.euiButton__text').contains('Delete').click(); }; +export const uniqueId = Date.now(); export const baseQuery = 'source = opensearch_dashboards_sample_data_flights'; -export const nameOne = 'Cypress'; -export const nameTwo = 'Pine'; +export const nameOne = `Cypress-${uniqueId}`; +export const nameTwo = `Pine-${uniqueId}`; export const description = 'This is my application for cypress testing.'; export const service_one = 'order'; export const service_two = 'payment'; @@ -84,4 +85,4 @@ export const query_two = 'where OriginCityName = "Seoul" | stats count() by span export const visOneName = 'Flights to Venice'; export const visTwoName = 'Flights from Seoul'; export const composition = 'order, payment, HTTP POST, HTTP GET, client_pay_order' -export const newName = 'Monterey Cypress'; \ No newline at end of file +export const newName = `Monterey Cypress-${uniqueId}`; diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx index 9eeded950..dd766ce0b 100644 --- a/dashboards-observability/public/components/application_analytics/components/application.tsx +++ b/dashboards-observability/public/components/application_analytics/components/application.tsx @@ -371,7 +371,6 @@ export function Application(props: AppDetailProps) { appBaseQuery={application.baseQuery} callback={callback} callbackInApp={callbackInApp} - // setAvailability={triggerSetAvailability} curSelectedTabId={selectedTabId} /> ); diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx index 16d85afcd..f7552b4b7 100644 --- a/dashboards-observability/public/components/application_analytics/components/create.tsx +++ b/dashboards-observability/public/components/application_analytics/components/create.tsx @@ -126,13 +126,15 @@ export const CreateApp = (props: CreateAppProps) => { const isDisabled = !name || (!query && !selectedTraces.length && !selectedServices.length); - const missingField = () => { - if (isDisabled) { - let popoverContent = ''; + const missingField = (needLog: boolean) => { + let popoverContent = ''; + if (isDisabled || (needLog && !query)) { if (!name) { popoverContent = 'Name is required.'; } else if (!query && !selectedServices.length && !selectedTraces.length) { popoverContent = 'Provide at least one log source, service, entity or trace group.'; + } else if (needLog && !query) { + popoverContent = 'Log source is required to set availability.'; } return

{popoverContent}

; } @@ -235,9 +237,10 @@ export const CreateApp = (props: CreateAppProps) => {
- + onCreate('createSetAvailability')} > Create and Set Availability @@ -245,7 +248,7 @@ export const CreateApp = (props: CreateAppProps) => { - + Date: Thu, 5 May 2022 15:09:57 -0700 Subject: [PATCH 06/14] Start fixing cypress tests Signed-off-by: Eugene Lee --- .../.cypress/integration/app_analytics.spec.js | 6 ++++-- dashboards-observability/.cypress/utils/app_constants.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dashboards-observability/.cypress/integration/app_analytics.spec.js b/dashboards-observability/.cypress/integration/app_analytics.spec.js index ce7de541c..34284365d 100644 --- a/dashboards-observability/.cypress/integration/app_analytics.spec.js +++ b/dashboards-observability/.cypress/integration/app_analytics.spec.js @@ -39,15 +39,17 @@ describe('Creating application', () => { }); it('Disables create button if missing fields', () => { - expectMessageOnHover('Name is required.'); + expectMessageOnHover('createButton', 'Name is required.'); cy.get('[data-test-subj="nameFormRow"]').type(nameOne); - expectMessageOnHover('Provide at least one log source, service, entity or trace group.'); + expectMessageOnHover('createButton', 'Provide at least one log source, service, entity or trace group.'); cy.get('[data-test-subj="servicesEntitiesAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="servicesEntitiesComboBox"]').trigger('mouseover').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); cy.get('[data-test-subj="servicesEntitiesCountBadge"]').should('contain', '1'); cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); + cy.get('[data-test-subj="createAndSetButton"]').should('be.disabled'); + expectMessageOnHover('createAndSetButton', 'Log source is required to set availability.'); cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); cy.get('[data-test-subj="traceGroupsAccordion"]').scrollIntoView(); diff --git a/dashboards-observability/.cypress/utils/app_constants.js b/dashboards-observability/.cypress/utils/app_constants.js index a9ffc9863..67e099b83 100644 --- a/dashboards-observability/.cypress/utils/app_constants.js +++ b/dashboards-observability/.cypress/utils/app_constants.js @@ -50,8 +50,8 @@ export const changeTimeTo24 = (timeUnit) => { cy.get('.euiButton').contains('Refresh').click(); }; -export const expectMessageOnHover = (message) => { - cy.get('.euiToolTipAnchor').contains('Create').click({ force: true }); +export const expectMessageOnHover = (button, message) => { + cy.get(`[data-test-subj="${button}"]`).click({ force: true }); cy.get('.euiToolTipPopover').contains(message).should('exist'); }; From eb7247c825579e010910571bc5ec64baad853c52 Mon Sep 17 00:00:00 2001 From: Eugene Lee Date: Tue, 10 May 2022 13:45:50 -0700 Subject: [PATCH 07/14] Add cypress tests and minor UI changes Signed-off-by: Eugene Lee --- .../integration/app_analytics.spec.js | 73 ++- .../.cypress/utils/app_constants.js | 4 +- .../__snapshots__/create.test.tsx.snap | 414 ++++++++++++++- .../components/app_table.tsx | 5 +- .../components/configuration.tsx | 7 +- .../components/create.tsx | 27 +- .../components/application_analytics/home.tsx | 1 + .../components/common/search/search.tsx | 1 + .../__snapshots__/utils.test.tsx.snap | 72 +++ .../__snapshots__/config_panel.test.tsx.snap | 481 +++++++++++++++++- .../config_panel/config_panel_footer.tsx | 5 +- .../config_controls/config_availability.tsx | 8 +- .../__snapshots__/search_bar.test.tsx.snap | 10 +- .../components/common/search_bar.tsx | 5 +- .../__snapshots__/dashboard.test.tsx.snap | 20 +- .../__snapshots__/services.test.tsx.snap | 20 +- .../__snapshots__/traces.test.tsx.snap | 20 +- .../__tests__/__snapshots__/bar.test.tsx.snap | 6 + .../__snapshots__/data_table.test.tsx.snap | 6 + .../__snapshots__/heatmap.test.tsx.snap | 6 + .../__snapshots__/line.test.tsx.snap | 6 + .../__tests__/__snapshots__/pie.test.tsx.snap | 6 + .../__snapshots__/text.test.tsx.snap | 6 + 23 files changed, 1093 insertions(+), 116 deletions(-) diff --git a/dashboards-observability/.cypress/integration/app_analytics.spec.js b/dashboards-observability/.cypress/integration/app_analytics.spec.js index 34284365d..f2a7b838c 100644 --- a/dashboards-observability/.cypress/integration/app_analytics.spec.js +++ b/dashboards-observability/.cypress/integration/app_analytics.spec.js @@ -16,6 +16,7 @@ import { baseQuery, nameOne, nameTwo, + nameThree, description, service_one, service_two, @@ -24,6 +25,7 @@ import { trace_three, query_one, query_two, + availability_default, visOneName, visTwoName, composition, @@ -38,29 +40,6 @@ describe('Creating application', () => { moveToCreatePage(); }); - it('Disables create button if missing fields', () => { - expectMessageOnHover('createButton', 'Name is required.'); - cy.get('[data-test-subj="nameFormRow"]').type(nameOne); - expectMessageOnHover('createButton', 'Provide at least one log source, service, entity or trace group.'); - cy.get('[data-test-subj="servicesEntitiesAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="servicesEntitiesComboBox"]').trigger('mouseover').click(); - cy.focused().type('{downArrow}'); - cy.focused().type('{enter}'); - cy.get('[data-test-subj="servicesEntitiesCountBadge"]').should('contain', '1'); - cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); - cy.get('[data-test-subj="createAndSetButton"]').should('be.disabled'); - expectMessageOnHover('createAndSetButton', 'Log source is required to set availability.'); - cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); - cy.get('[data-test-subj="traceGroupsAccordion"]').scrollIntoView(); - cy.get('[data-test-subj="traceGroupsAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="traceGroupsComboBox"]').scrollIntoView().type('http'); - cy.get('.euiFilterSelectItem').contains(trace_one).click({ force: true }); - cy.get('.euiFilterSelectItem').contains(trace_two).click({ force: true }); - cy.get('[data-test-subj="traceGroupsCountBadge"]').should('contain', '2'); - cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); - }); - it('Suggests correct autocompletion', () => { cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="searchAutocompleteTextArea"]').click(); @@ -93,14 +72,19 @@ describe('Creating application', () => { }); it('Creates an application and redirects to application', () => { + expectMessageOnHover('createButton', 'Name is required.'); cy.get('[data-test-subj="nameFormRow"]').type(nameOne); cy.get('[data-test-subj="descriptionFormRow"]').type('This application is for testing.'); + expectMessageOnHover('createButton', 'Provide at least one log source, service, entity or trace group.'); cy.get('[data-test-subj="servicesEntitiesAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="servicesEntitiesComboBox"]').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); cy.get('[data-test-subj="servicesEntitiesCountBadge"]').should('contain', '1'); cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); + cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); + cy.get('[data-test-subj="createAndSetButton"]').should('be.disabled'); + expectMessageOnHover('createAndSetButton', 'Log source is required to set availability.'); cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); cy.get('[data-test-subj="traceGroupsAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="traceGroupsComboBox"]').scrollIntoView().type('http'); @@ -200,6 +184,35 @@ describe('Creating application', () => { }); }); +describe('Setting availability', () => { + it('Redirects to set availability at three entry points', () => { + moveToCreatePage(); + cy.get('[data-test-subj="nameFormRow"]').type(nameThree); + cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus(); + cy.focused().type('source = ', { delay: TYPING_DELAY }); + cy.focused().type('{enter}'); + cy.get('[data-test-subj="createAndSetButton"]').click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get('[data-test-subj="setAvailabilityHomePageLink"]').first().click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree); + cy.get('.euiTab-isSelected[id="app-analytics-log"]').should('exist', { timeout: timeoutDelay }); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').should('contain.value', availability_default); + cy.get('[id="explorerPlotComponent"]').should('exist'); + cy.get('.euiTab-isSelected[id="availability-panel"]').should('exist'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get(`[data-test-subj="${nameThree}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree); + cy.get('[data-test-subj="app-analytics-configTab"]').click(); + cy.get('[data-test-subj="setAvailabilityConfigLink"]').click(); + cy.get('.euiTab-isSelected[id="app-analytics-log"]').should('exist', { timeout: timeoutDelay }); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').should('contain.value', availability_default); + cy.get('[id="explorerPlotComponent"]').should('exist'); + cy.get('.euiTab-isSelected[id="availability-panel"]').should('exist'); + }); +}); + describe('Viewing application', () => { beforeEach(() => { moveToApplication(nameOne); @@ -237,7 +250,7 @@ describe('Viewing application', () => { it('Adds filter when Trace group name is clicked', () => { cy.get('[data-test-subj="app-analytics-overviewTab"]').click(); - cy.get('.euiLink').contains('client_create_order').click(); + cy.get('[data-test-subj="dashboard-table-trace-group-name-button"]').contains('client_create_order').click(); cy.get('.euiTableRow').should('have.length', 1); cy.get('[data-test-subj="client_create_orderFilterBadge"]').should('exist'); cy.get('[data-test-subj="filterBadge"]').click(); @@ -326,6 +339,7 @@ describe('Viewing application', () => { cy.get('[data-test-subj="eventExplorer__querySaveName"]').click().type(visOneName); cy.wait(delay); cy.get('[data-test-subj="eventExplorer__querySaveConfirm"]').click(); + cy.wait(delay); cy.get('[data-test-subj="app-analytics-panelTab"]').click(); cy.wait(delay); cy.get('[data-test-subj="Flights to VeniceVisualizationPanel"]').should('exist'); @@ -339,10 +353,11 @@ describe('Viewing application', () => { cy.get('[data-test-subj="editVizContextMenuItem"]').click(); supressResizeObserverIssue(); cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); + cy.get('.euiTab[id="availability-panel"]').click(); cy.get('[title="Bar"]').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); - cy.get('[data-test-subj="addThresholdButton"]').click(); + cy.get('[data-test-subj="addAvailabilityButton"]').click(); cy.get('[data-test-subj="euiColorPickerAnchor"]').click(); cy.get('[aria-label="Select #54B399 as the color"]').click(); cy.get('[data-test-subj="nameFieldText"]').click().type('Available'); @@ -375,11 +390,12 @@ describe('Viewing application', () => { cy.wait(delay); cy.get('[data-test-subj="main-content-visTab"]').click(); supressResizeObserverIssue(); + cy.get('.euiTab[id="availability-panel"]').click(); cy.get('[title="Bar"]').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); cy.wait(delay); - cy.get('[data-test-subj="addThresholdButton"]').click(); + cy.get('[data-test-subj="addAvailabilityButton"]').click(); cy.get('[data-test-subj="euiColorPickerAnchor"]').click(); cy.get('[aria-label="Select #9170B8 as the color"]').click(); cy.wait(delay); @@ -388,7 +404,7 @@ describe('Viewing application', () => { cy.get('[data-test-subj="valueFieldNumber"]').clear().type('5.5'); cy.get('[data-test-subj="visualizeEditorRenderButton"]').click(); cy.wait(delay); - cy.get('[data-test-subj="addThresholdButton"]').click(); + cy.get('[data-test-subj="addAvailabilityButton"]').click(); cy.get('[data-test-subj="euiColorPickerAnchor"]').first().click(); cy.get('[aria-label="Select #CA8EAE as the color"]').click(); cy.wait(delay); @@ -472,8 +488,9 @@ describe('Separate from other plugins', () => { cy.get('[data-test-subj="operationalPanelsActionsButton"]', { timeout: timeoutDelay }).click(); cy.get('[data-test-subj="addSampleContextMenuItem"]', { timeout: timeoutDelay }).click(); cy.get('[data-test-subj="confirmModalConfirmButton"]', { timeout: timeoutDelay }).click(); + cy.wait(delay * 2); cy.get('.euiLink').contains('[Logs] Web traffic Panel').first().click(); - cy.wait(delay); + cy.wait(delay * 2); cy.get('[data-test-subj="addVisualizationButton"]').click(); cy.get('.euiContextMenuItem__text').contains('Select existing visualization').click(); cy.get('option').contains(visOneName).should('not.exist'); diff --git a/dashboards-observability/.cypress/utils/app_constants.js b/dashboards-observability/.cypress/utils/app_constants.js index 67e099b83..f87432409 100644 --- a/dashboards-observability/.cypress/utils/app_constants.js +++ b/dashboards-observability/.cypress/utils/app_constants.js @@ -47,7 +47,7 @@ export const changeTimeTo24 = (timeUnit) => { cy.get('[aria-label="Time unit"]').select(timeUnit); cy.get('.euiButton').contains('Apply').click(); cy.wait(delay); - cy.get('.euiButton').contains('Refresh').click(); + cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); }; export const expectMessageOnHover = (button, message) => { @@ -74,6 +74,7 @@ export const uniqueId = Date.now(); export const baseQuery = 'source = opensearch_dashboards_sample_data_flights'; export const nameOne = `Cypress-${uniqueId}`; export const nameTwo = `Pine-${uniqueId}`; +export const nameThree = `Cedar-${uniqueId}`; export const description = 'This is my application for cypress testing.'; export const service_one = 'order'; export const service_two = 'payment'; @@ -82,6 +83,7 @@ export const trace_two = 'HTTP GET'; export const trace_three = 'client_pay_order'; export const query_one = 'where DestCityName = "Venice" | stats count() by span( timestamp , 6h )'; export const query_two = 'where OriginCityName = "Seoul" | stats count() by span( timestamp , 6h )'; +export const availability_default = 'stats count() by span( timestamp, 1h )'; export const visOneName = 'Flights to Venice'; export const visTwoName = 'Flights from Seoul'; export const composition = 'order, payment, HTTP POST, HTTP GET, client_pay_order' diff --git a/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap b/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap index c8710d638..fd2d3f4e4 100644 --- a/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap +++ b/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap @@ -1096,7 +1096,7 @@ Object { class="euiToolTipAnchor" >
+
+ + + +
@@ -2325,7 +2349,7 @@ Object { class="euiToolTipAnchor" > + + @@ -3527,7 +3575,7 @@ Object { class="euiToolTipAnchor" > + + @@ -4670,7 +4742,7 @@ Object { class="euiToolTipAnchor" > + + @@ -5872,7 +5968,7 @@ Object { class="euiToolTipAnchor" > + + @@ -7015,7 +7135,7 @@ Object { class="euiToolTipAnchor" > + + @@ -8152,7 +8296,7 @@ Object { class="euiToolTipAnchor" > + + @@ -9232,7 +9400,7 @@ Object { class="euiToolTipAnchor" > + + @@ -10400,7 +10592,7 @@ Object { class="euiToolTipAnchor" > +
+ + + +
@@ -11510,7 +11725,7 @@ Object { class="euiToolTipAnchor" > +
+ + + +
@@ -12711,7 +12949,7 @@ Object { class="euiToolTipAnchor" > + + @@ -13854,7 +14116,7 @@ Object { class="euiToolTipAnchor" > + + @@ -15056,7 +15342,7 @@ Object { class="euiToolTipAnchor" > + + @@ -16199,7 +16509,7 @@ Object { class="euiToolTipAnchor" > + + @@ -17371,7 +17705,7 @@ Object { class="euiToolTipAnchor" > + + @@ -18596,7 +18954,7 @@ Object { class="euiToolTipAnchor" > + + diff --git a/dashboards-observability/public/components/application_analytics/components/app_table.tsx b/dashboards-observability/public/components/application_analytics/components/app_table.tsx index 93d85d996..ac318a554 100644 --- a/dashboards-observability/public/components/application_analytics/components/app_table.tsx +++ b/dashboards-observability/public/components/application_analytics/components/app_table.tsx @@ -200,7 +200,10 @@ export function AppTable(props: AppTableProps) { return Current value is null; } else { return ( - moveToApp(record.id, 'createSetAvailability')}> + moveToApp(record.id, 'createSetAvailability')} + > Set Availability ); diff --git a/dashboards-observability/public/components/application_analytics/components/configuration.tsx b/dashboards-observability/public/components/application_analytics/components/configuration.tsx index 0d190b9f5..c8c4ce2cb 100644 --- a/dashboards-observability/public/components/application_analytics/components/configuration.tsx +++ b/dashboards-observability/public/components/application_analytics/components/configuration.tsx @@ -132,7 +132,12 @@ export const Configuration = (props: ConfigProps) => { onChange={onAvailabilityVisChange} /> ) : ( - switchToAvailability()}>Set Availability + switchToAvailability()} + > + Set Availability + )}
diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx index f7552b4b7..390aa3612 100644 --- a/dashboards-observability/public/components/application_analytics/components/create.tsx +++ b/dashboards-observability/public/components/application_analytics/components/create.tsx @@ -236,29 +236,32 @@ export const CreateApp = (props: CreateAppProps) => { Cancel - - - onCreate('createSetAvailability')} - > - Create and Set Availability - - - onCreate('create')} - fill + fill={editMode ? true : false} > {editMode ? 'Save' : 'Create'} + {editMode || ( + + + onCreate('createSetAvailability')} + > + Create and Set Availability + + + + )} diff --git a/dashboards-observability/public/components/application_analytics/home.tsx b/dashboards-observability/public/components/application_analytics/home.tsx index a596d5291..1d837d437 100644 --- a/dashboards-observability/public/components/application_analytics/home.tsx +++ b/dashboards-observability/public/components/application_analytics/home.tsx @@ -322,6 +322,7 @@ export const Home = (props: HomeProps) => { if (type === 'update') { setToast('Application successfully updated.'); clearStorage(); + moveToApp(res.updatedAppId, type); } if (type.startsWith('create')) { moveToApp(res.updatedAppId, type); diff --git a/dashboards-observability/public/components/common/search/search.tsx b/dashboards-observability/public/components/common/search/search.tsx index f1771338c..2cea91e63 100644 --- a/dashboards-observability/public/components/common/search/search.tsx +++ b/dashboards-observability/public/components/common/search/search.tsx @@ -111,6 +111,7 @@ export const Search = (props: any) => { }); }} data-test-subj="eventExplorer__saveManagementPopover" + fill iconType="arrowDown" > Save diff --git a/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap b/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap index aa8a1a9f4..9fea351f9 100644 --- a/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap +++ b/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap @@ -170,6 +170,12 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -378,6 +384,12 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -609,6 +621,12 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -983,6 +1001,12 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -1208,6 +1232,12 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -1487,6 +1517,12 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -1912,6 +1948,12 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -2120,6 +2162,12 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -2351,6 +2399,12 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -2710,6 +2764,12 @@ exports[`Utils helper functions renders displayVisualization function 4`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -2891,6 +2951,12 @@ exports[`Utils helper functions renders displayVisualization function 4`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -3095,6 +3161,12 @@ exports[`Utils helper functions renders displayVisualization function 4`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap index 69eda470d..c33a79f8b 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap @@ -282,6 +282,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -452,6 +458,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -574,6 +586,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -715,6 +733,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Pie", @@ -808,6 +832,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Hubble", @@ -1030,6 +1060,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -1197,6 +1233,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -1374,6 +1416,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], } } @@ -1481,6 +1529,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -1652,6 +1706,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], } } @@ -1794,6 +1854,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], } } @@ -2069,9 +2135,10 @@ exports[`Config panel component Renders config panel with visualization data 1`] className="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow" > , + "id": "availability-panel", + "name": "Availability", + }, ] } >
+ + +