Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dashboards-observability/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const App = ({
parentBreadcrumb={parentBreadcrumb}
renderProps={props}
pplService={pplService}
savedObjects={savedObjects}
/>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,33 @@ export const mergeLayoutAndVisualizations = (
setPanelVisualizations(newPanelVisualizations);
};

/* Update Span interval for a Query
* Input query -> source = opensearch_dashboards_sample_data_logs | stats avg(bytes) by span(timestamp,1d)
* spanParam -> 1M
*
* Updates the span command interval
* Returns -> source = opensearch_dashboards_sample_data_logs | stats avg(bytes) by span(timestamp,1M)
*/
export const updateQuerySpanInterval = (
query: string,
timestampField: string,
spanParam: string
) => {
return query.replace(
new RegExp(`span\\((.*?)${timestampField}(.*?),(.*?)\\)`),
`span(${timestampField},${spanParam})`
);
};

/* Builds Final Query by adding time and query filters(From panel UI) to the original visualization query
* -> Final Query is as follows:
* -> finalQuery = indexPartOfQuery + timeQueryFilter + panelFilterQuery + filterPartOfQuery
* -> finalQuery = source=opensearch_dashboards_sample_data_flights
* + | where utc_time > ‘2021-07-01 00:00:00’ and utc_time < ‘2021-07-02 00:00:00’
* + | where Carrier='OpenSearch-Air'
* + | stats sum(FlightDelayMin) as delays by Carrier
*
* Also, checks is span interval update is needed and retruns accordingly
*/
const queryAccumulator = (
originalQuery: string,
Expand All @@ -103,15 +123,12 @@ const queryAccumulator = (
startTime
)}' and ${timestampField} <= '${convertDateTime(endTime, false)}'`;
const pplFilterQuery = panelFilterQuery === '' ? '' : ` | ${panelFilterQuery}`;

const finalQuery = indexPartOfQuery + timeQueryFilter + pplFilterQuery + filterPartOfQuery;
if (spanParam === undefined) {
return finalQuery;
} else {
return finalQuery.replace(
new RegExp(`span\\(${timestampField},(.*?)\\)`),
`span(${timestampField},${spanParam})`
);
}

return spanParam === undefined
? finalQuery
: updateQuerySpanInterval(finalQuery, timestampField, spanParam);
};

// PPL Service requestor
Expand Down Expand Up @@ -400,7 +417,7 @@ export const displayVisualization = (metaData: any, data: any, type: string) =>
),
},
};

return (
<Visualization
visualizations={getVizContainerProps({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { CoreStart } from '../../../../../../src/core/public';
import { MetricType } from '../../../../common/types/metrics';
import { VisualizationType } from '../../../../common/types/custom_panels';
import { DEFAULT_METRIC_HEIGHT, DEFAULT_METRIC_WIDTH } from '../../../../common/constants/metrics';
import { UNITS_OF_MEASURE } from '../../../../common/constants/explorer';
import { updateQuerySpanInterval } from '../../custom_panels/helpers/utils';

export const onTimeChange = (
start: ShortDate,
Expand Down Expand Up @@ -161,3 +163,52 @@ export const mergeLayoutAndMetrics = (
}
return newPanelVisualizations;
};

export const sortMetricLayout = (metricsLayout: MetricType[]) => {
return metricsLayout.sort((a: MetricType, b: MetricType) => {
if (a.y > b.y) return 1;
if (a.y < b.y) return -1;
else return 0;
});
};

export const createPrometheusMetricById = (metricId: string) => {
return {
name: '[Prometheus Metric] ' + metricId,
description: '',
query: 'source = ' + metricId + ' | stats avg(@value) by span(@timestamp,1h)',
type: 'line',
timeField: '@timestamp',
selected_fields: {
text: '',
tokens: [],
},
sub_type: 'metric',
units_of_measure: UNITS_OF_MEASURE[1],
user_configs: {},
};
};

export const updateMetricsWithSelections = (
savedVisualization: any,
startTime: ShortDate,
endTime: ShortDate,
spanValue: string
) => {
return {
query: updateQuerySpanInterval(
savedVisualization.query,
savedVisualization.timeField,
spanValue
),
fields: savedVisualization.selected_fields.tokens,
dateRange: [startTime, endTime],
timestamp: savedVisualization.timeField,
name: savedVisualization.name,
description: savedVisualization.description,
type: 'line',
subType: 'metric',
userConfigs: JSON.stringify(savedVisualization.user_configs),
unitsOfMeasure: savedVisualization.units_of_measure,
};
};
33 changes: 31 additions & 2 deletions dashboards-observability/public/components/metrics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import './index.scss';
import {
EuiButtonIcon,
EuiGlobalToastList,
EuiPage,
EuiPageBody,
htmlIdGenerator,
Expand All @@ -17,7 +18,7 @@ import React, { useEffect, useState } from 'react';
import { Route, RouteComponentProps } from 'react-router-dom';
import classNames from 'classnames';
import { StaticContext } from 'react-router-dom';
import { ChromeBreadcrumb, CoreStart } from '../../../../../src/core/public';
import { ChromeBreadcrumb, CoreStart, Toast } from '../../../../../src/core/public';
import { onTimeChange } from './helpers/utils';
import { Sidebar } from './sidebar/sidebar';
import { EmptyMetricsView } from './view/empty_view';
Expand All @@ -28,16 +29,25 @@ import { MetricsGrid } from './view/metrics_grid';
import { useSelector } from 'react-redux';
import { metricsLayoutSelector, selectedMetricsSelector } from './redux/slices/metrics_slice';
import { resolutionOptions } from '../../../common/constants/metrics';
import SavedObjects from '../../services/saved_objects/event_analytics/saved_objects';

interface MetricsProps {
http: CoreStart['http'];
chrome: CoreStart['chrome'];
parentBreadcrumb: ChromeBreadcrumb;
renderProps: RouteComponentProps<any, StaticContext, any>;
pplService: PPLService;
savedObjects: SavedObjects;
}

export const Home = ({ http, chrome, parentBreadcrumb, renderProps, pplService }: MetricsProps) => {
export const Home = ({
http,
chrome,
parentBreadcrumb,
renderProps,
pplService,
savedObjects,
}: MetricsProps) => {
// Redux tools
const selectedMetrics = useSelector(selectedMetricsSelector);
const metricsLayout = useSelector(metricsLayoutSelector);
Expand All @@ -55,13 +65,21 @@ export const Home = ({ http, chrome, parentBreadcrumb, renderProps, pplService }
const [resolutionValue, setResolutionValue] = useState(resolutionOptions[2].value);
const [spanValue, setSpanValue] = useState(1);
const resolutionSelectId = htmlIdGenerator('resolutionSelect')();
const [toasts, setToasts] = useState<Toast[]>([]);
const [toastRightSide, setToastRightSide] = useState<boolean>(true);

// Side bar constants
const [isSidebarClosed, setIsSidebarClosed] = useState(false);

// Metrics constants
const [panelVisualizations, setPanelVisualizations] = useState<MetricType[]>([]);

const setToast = (title: string, color = 'success', text?: ReactChild, side?: string) => {
if (!text) text = '';
setToastRightSide(!side ? true : false);
setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]);
};

const onRefreshFilters = (startTime: ShortDate, endTime: ShortDate) => {
setOnRefresh(!onRefresh);
};
Expand Down Expand Up @@ -114,6 +132,14 @@ export const Home = ({ http, chrome, parentBreadcrumb, renderProps, pplService }

return (
<>
<EuiGlobalToastList
toasts={toasts}
dismissToast={(removedToast) => {
setToasts(toasts.filter((toast) => toast.id !== removedToast.id));
}}
side={toastRightSide ? 'right' : 'left'}
toastLifeTimeMs={6000}
/>
<Route
exact
path="/metrics"
Expand All @@ -122,6 +148,7 @@ export const Home = ({ http, chrome, parentBreadcrumb, renderProps, pplService }
<EuiPage>
<EuiPageBody component="div">
<TopMenu
http={http}
IsTopPanelDisabled={IsTopPanelDisabled}
startTime={startTime}
endTime={endTime}
Expand All @@ -137,6 +164,8 @@ export const Home = ({ http, chrome, parentBreadcrumb, renderProps, pplService }
spanValue={spanValue}
setSpanValue={setSpanValue}
resolutionSelectId={resolutionSelectId}
savedObjects={savedObjects}
setToast={setToast}
/>
<div className="dscAppContainer">
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { CUSTOM_PANELS_API_PREFIX } from '../../../../common/constants/custom_panels';
import React, { useEffect, useState } from 'react';
import { CoreStart } from '../../../../../../src/core/public';
import {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFieldText,
EuiFlexGroup,
EuiFormRow,
EuiFlexItem,
EuiForm,
EuiSelect,
} from '@elastic/eui';
import { UNITS_OF_MEASURE } from '../../../../common/constants/explorer';
import { createPrometheusMetricById } from '../helpers/utils';
import { MetricType } from '../../../../common/types/metrics';
import { fetchVisualizationById } from '../../custom_panels/helpers/utils';

interface MetricsExportPanelProps {
http: CoreStart['http'];
visualizationsMetaData: any;
setVisualizationsMetaData: React.Dispatch<any>;
sortedMetricsLayout: MetricType[];
selectedPanelOptions: EuiComboBoxOptionOption<unknown>[] | undefined;
setSelectedPanelOptions: React.Dispatch<
React.SetStateAction<EuiComboBoxOptionOption<unknown>[] | undefined>
>;
}

interface CustomPanelOptions {
id: string;
name: string;
dateCreated: string;
dateModified: string;
}

export const MetricsExportPanel = ({
http,
visualizationsMetaData,
setVisualizationsMetaData,
sortedMetricsLayout,
selectedPanelOptions,
setSelectedPanelOptions,
}: MetricsExportPanelProps) => {
const [options, setOptions] = useState([]);

const [errorResponse, setErrorResponse] = useState('');

const getCustomPanelList = async () => {
http
.get(`${CUSTOM_PANELS_API_PREFIX}/panels`)
.then((res: any) => {
setOptions(res.panels || []);
})
.catch((error: any) => console.error(error));
};

const fetchAllvisualizationsById = async () => {
let tempVisualizationsMetaData = await Promise.all(
sortedMetricsLayout.map(async (metricLayout) => {
return metricLayout.metricType === 'savedCustomMetric'
? await fetchVisualizationById(http, metricLayout.id, setErrorResponse)
: createPrometheusMetricById(metricLayout.id);
})
);
console.log('tempVisualizationsMetaData', tempVisualizationsMetaData);
setVisualizationsMetaData(tempVisualizationsMetaData);
};

useEffect(() => {
getCustomPanelList();
fetchAllvisualizationsById();
}, []);

const onNameChange = (index: number, name: string) => {
let tempVisualizationsMetaData = [...visualizationsMetaData];
tempVisualizationsMetaData[index].name = name;
setVisualizationsMetaData(tempVisualizationsMetaData);
};

const onMeasureChange = (index: number, measureOption: any) => {
let tempVisualizationsMetaData = [...visualizationsMetaData];
tempVisualizationsMetaData[index].units_of_measure = measureOption;
setVisualizationsMetaData(tempVisualizationsMetaData);
};

return (
<div style={{ minWidth: '25vw' }}>
<EuiFormRow
label="Custom operational dashboards/application"
helpText="Search existing dashboards or applications by name"
>
<EuiComboBox
placeholder="Select dashboards/applications"
onChange={(options) => {
setSelectedPanelOptions(options);
}}
selectedOptions={selectedPanelOptions}
options={options.map((option: CustomPanelOptions) => {
return {
panel: option,
label: option.name,
};
})}
isClearable={true}
data-test-subj="eventExplorer__querySaveComboBox"
/>
</EuiFormRow>

{visualizationsMetaData.length > 0 && (
<div style={{ maxHeight: '30vh', overflowY: 'scroll', width: 'auto', overflowX: 'hidden' }}>
{visualizationsMetaData.map((metaData: any, index: number) => {
return (
<EuiForm component="form">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow label={'Metric Name #' + (index + 1)}>
<EuiFieldText
key={'save-panel-id'}
value={visualizationsMetaData[index].name}
onChange={(e) => onNameChange(index, e.target.value)}
data-test-subj="metrics__querySaveName"
/>
</EuiFormRow>
</EuiFlexItem>

<EuiFlexItem>
<EuiFormRow label="Units of Measure">
<EuiSelect
id={'selector' + index}
options={UNITS_OF_MEASURE.map((i) => {
return { value: i, text: i };
})}
value={visualizationsMetaData[index].units_of_measure}
onChange={(e) => onMeasureChange(index, e.target.value)}
data-test-subj="metrics__measureSelector"
aria-label="metrics__measureSelector"
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
);
})}
</div>
)}
</div>
);
};
Loading