diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx
index fb55a6ced..ba5132be7 100644
--- a/dashboards-observability/public/components/application_analytics/components/application.tsx
+++ b/dashboards-observability/public/components/application_analytics/components/application.tsx
@@ -57,7 +57,10 @@ import { IQueryTab } from '../../../../common/types/explorer';
import { NotificationsStart } from '../../../../../../src/core/public';
import { AppAnalyticsComponentDeps } from '../home';
import { CustomPanelView } from '../../../../public/components/custom_panels/custom_panel_view';
-import { ApplicationType } from '../../../../common/types/app_analytics';
+import {
+ ApplicationRequestType,
+ ApplicationType,
+} from '../../../../common/types/application_analytics';
import { CUSTOM_PANELS_API_PREFIX } from '../../../../common/constants/custom_panels';
import { ServiceDetailFlyout } from './flyout_components/service_detail_flyout';
import { SpanDetailFlyout } from '../../../../public/components/trace_analytics/components/traces/span_detail_flyout';
@@ -83,7 +86,7 @@ interface AppDetailProps extends AppAnalyticsComponentDeps {
savedObjects: SavedObjects;
timestampUtils: TimestampUtils;
notifications: NotificationsStart;
- updateApp: (appId: string, updateAppData: Partial
, type: string) => void;
+ updateApp: (appId: string, updateAppData: Partial, type: string) => void;
setToasts: (title: string, color?: string, text?: ReactChild) => void;
callback: (childfunction: () => void) => void;
}
@@ -109,13 +112,16 @@ export function Application(props: AppDetailProps) {
callback,
} = props;
const [application, setApplication] = useState({
+ id: '',
+ dateCreated: '',
+ dateModified: '',
name: '',
description: '',
baseQuery: '',
servicesEntities: [],
traceGroups: [],
panelId: '',
- availabilityVisId: '',
+ availability: { name: '', color: '', availabilityVisId: '' },
});
const dispatch = useDispatch();
const [triggerAvailability, setTriggerAvailability] = useState(false);
@@ -381,7 +387,11 @@ export function Application(props: AppDetailProps) {
};
const updateAvailabilityVizId = (vizs: VisualizationType[]) => {
- if (!vizs.map((viz) => viz.savedVisualizationId).includes(application.availabilityVisId)) {
+ if (
+ !vizs
+ .map((viz) => viz.savedVisualizationId)
+ .includes(application.availability.availabilityVisId)
+ ) {
updateApp(appId, { availabilityVisId: '' }, 'editAvailability');
}
};
diff --git a/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx b/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx
index 569cb8609..30b78adcd 100644
--- a/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx
+++ b/dashboards-observability/public/components/application_analytics/components/config_components/service_config.tsx
@@ -22,7 +22,7 @@ import { ServiceObject } from '../../../trace_analytics/components/common/plots/
import { ServiceMap } from '../../../trace_analytics/components/services';
import { handleServiceMapRequest } from '../../../trace_analytics/requests/services_request_handler';
import { AppAnalyticsComponentDeps } from '../../home';
-import { OptionType } from '../../../../../common/types/app_analytics';
+import { OptionType } from '../../../../../common/types/application_analytics';
import { getClearModal } from '../../helpers/modal_containers';
interface ServiceConfigProps extends AppAnalyticsComponentDeps {
diff --git a/dashboards-observability/public/components/application_analytics/components/config_components/trace_config.tsx b/dashboards-observability/public/components/application_analytics/components/config_components/trace_config.tsx
index e7bcf1167..e0ff9a1bb 100644
--- a/dashboards-observability/public/components/application_analytics/components/config_components/trace_config.tsx
+++ b/dashboards-observability/public/components/application_analytics/components/config_components/trace_config.tsx
@@ -18,7 +18,7 @@ import {
import DSLService from 'public/services/requests/dsl';
import React, { useEffect, useState } from 'react';
import { FilterType } from 'public/components/trace_analytics/components/common/filters/filters';
-import { OptionType } from '../../../../../common/types/app_analytics';
+import { OptionType } from '../../../../../common/types/application_analytics';
import { filtersToDsl } from '../../../trace_analytics/components/common/helper_functions';
import { handleDashboardRequest } from '../../../trace_analytics/requests/dashboard_request_handler';
import { AppAnalyticsComponentDeps } from '../../home';
diff --git a/dashboards-observability/public/components/application_analytics/components/configuration.tsx b/dashboards-observability/public/components/application_analytics/components/configuration.tsx
index c8c4ce2cb..8cb8080ff 100644
--- a/dashboards-observability/public/components/application_analytics/components/configuration.tsx
+++ b/dashboards-observability/public/components/application_analytics/components/configuration.tsx
@@ -2,7 +2,6 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
-/* eslint-disable react-hooks/exhaustive-deps */
import {
EuiBreadcrumb,
@@ -24,7 +23,7 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
-import { ApplicationType } from 'common/types/app_analytics';
+import { ApplicationRequestType, ApplicationType } from 'common/types/application_analytics';
import { last } from 'lodash';
import React, { useState } from 'react';
@@ -34,7 +33,7 @@ interface ConfigProps {
parentBreadcrumbs: EuiBreadcrumb[];
visWithAvailability: EuiSelectOption[];
switchToAvailability: () => void;
- updateApp: (appId: string, updateAppData: Partial, type: string) => void;
+ updateApp: (appId: string, updateAppData: Partial, type: string) => void;
}
export const Configuration = (props: ConfigProps) => {
@@ -46,7 +45,9 @@ export const Configuration = (props: ConfigProps) => {
updateApp,
switchToAvailability,
} = props;
- const [availabilityVisId, setAvailabilityVisId] = useState(application.availabilityVisId || '');
+ const [availabilityVisId, setAvailabilityVisId] = useState(
+ application.availability.availabilityVisId || ''
+ );
const onAvailabilityVisChange = (event: any) => {
setAvailabilityVisId(event.target.value);
diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx
index 390aa3612..084d53b17 100644
--- a/dashboards-observability/public/components/application_analytics/components/create.tsx
+++ b/dashboards-observability/public/components/application_analytics/components/create.tsx
@@ -32,15 +32,19 @@ import { TraceConfig } from './config_components/trace_config';
import { ServiceConfig } from './config_components/service_config';
import { LogConfig } from './config_components/log_config';
import { PPLReferenceFlyout } from '../../../components/common/helpers';
-import { ApplicationType, OptionType } from '../../../../common/types/app_analytics';
+import {
+ ApplicationRequestType,
+ ApplicationType,
+ OptionType,
+} from '../../../../common/types/application_analytics';
import { fetchAppById } from '../helpers/utils';
interface CreateAppProps extends AppAnalyticsComponentDeps {
dslService: DSLService;
pplService: PPLService;
setToasts: (title: string, color?: string, text?: ReactChild) => void;
- createApp: (app: ApplicationType, type: string) => void;
- updateApp: (appId: string, updateAppData: Partial, type: string) => void;
+ createApp: (app: ApplicationRequestType, type: string) => void;
+ updateApp: (appId: string, updateAppData: Partial, type: string) => void;
clearStorage: () => void;
existingAppId: string;
}
@@ -70,13 +74,16 @@ export const CreateApp = (props: CreateAppProps) => {
const editMode = existingAppId !== 'undefined';
const [existingApp, setExistingApp] = useState({
+ id: existingAppId,
+ dateCreated: '',
+ dateModified: '',
name: '',
description: '',
baseQuery: '',
servicesEntities: [],
traceGroups: [],
panelId: '',
- availabilityVisId: '',
+ availability: { name: '', color: '', availabilityVisId: '' },
});
useEffect(() => {
diff --git a/dashboards-observability/public/components/application_analytics/components/flyout_components/availability_info_flyout.tsx b/dashboards-observability/public/components/application_analytics/components/flyout_components/availability_info_flyout.tsx
new file mode 100644
index 000000000..86e668f41
--- /dev/null
+++ b/dashboards-observability/public/components/application_analytics/components/flyout_components/availability_info_flyout.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiFlyoutBody,
+ EuiText,
+ EuiCodeBlock,
+ EuiFlyoutFooter,
+ EuiButton,
+} from '@elastic/eui';
+import React from 'react';
+
+interface AvailabilityInfoFlyoutProps {
+ closeFlyout: () => void;
+}
+
+export function AvailabilityInfoFlyout(props: AvailabilityInfoFlyoutProps) {
+ const { closeFlyout } = props;
+
+ return (
+
+
+
+ Availability
+
+
+
+
+ Configure availability
+ Availability is the status of your application determined by availability levels set on a
+ time series metric. To create an availability level, you must configure the following:
+
+ - color: The color of the availability badge on the home page
+ - name: The text in the availability badge on the home page
+ - expression: Comparison operator to determine the availability
+ - value: Value to use when calculating availability
+
+ Configuring availability
+ By default, Application analytics shows results from the last 24 hours of your data. To
+ see data from a different timeframe, use the date and time selector.
+ Time series metric
A time series metric is any visualization that has a query that
+ spans over a timestamp and is a bar/line chart. You can use the PPL language to define
+ arbitrary conditions on your logs to create a visualization over time.
+ Example
+
+ {'source = | ... | ... | stats ... by span(, 1h)'}
+
+ You can then choose Bar or Line in visualization
+ configurations to create a time series metric.
+
+
+
+ Close
+
+
+ );
+}
diff --git a/dashboards-observability/public/components/application_analytics/helpers/types.tsx b/dashboards-observability/public/components/application_analytics/helpers/types.tsx
deleted file mode 100644
index 07ac03c9d..000000000
--- a/dashboards-observability/public/components/application_analytics/helpers/types.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * 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 d956fb06b..f212a3caf 100644
--- a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx
+++ b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx
@@ -5,7 +5,7 @@
/* eslint-disable no-console */
import { EuiDescriptionList, EuiSelectOption, EuiSpacer, EuiText } from '@elastic/eui';
-import { ApplicationListType, ApplicationType } from 'common/types/app_analytics';
+import { ApplicationType, AvailabilityType } from 'common/types/application_analytics';
import { FilterType } from 'public/components/trace_analytics/components/common/filters/filters';
import React, { Dispatch, ReactChild } from 'react';
import { batch } from 'react-redux';
@@ -36,7 +36,6 @@ 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[]) => {
@@ -100,18 +99,18 @@ export const fetchAppById = async (
) => {
return http
.get(`${APP_ANALYTICS_API_PREFIX}/${applicationId}`)
- .then(async (res) => {
- res.application.availabilityVisId = (
+ .then(async (res: ApplicationType) => {
+ res.availability.availabilityVisId = (
await calculateAvailability(
http,
pplService,
- res.application,
- res.application.availabilityVisId,
+ res,
+ res.availability.availabilityVisId,
setVisWithAvailability
)
- ).mainVisId;
- setApplication(res.application);
- const serviceFilters = res.application.servicesEntities.map((ser: string) => {
+ ).availabilityVisId;
+ setApplication(res);
+ const serviceFilters = res.servicesEntities.map((ser: string) => {
return {
field: 'serviceName',
operator: 'is',
@@ -120,7 +119,7 @@ export const fetchAppById = async (
disabled: false,
};
});
- const traceFilters = res.application.traceGroups.map((tra: string) => {
+ const traceFilters = res.traceGroups.map((tra: string) => {
return {
field: 'traceGroup',
operator: 'is',
@@ -194,11 +193,11 @@ export const fetchPanelsVizIdList = async (http: HttpSetup, appPanelId: string)
export const calculateAvailability = async (
http: HttpSetup,
pplService: PPLService,
- application: ApplicationType | ApplicationListType,
+ application: ApplicationType,
availabilityVisId: string,
setVisWithAvailability: (visList: EuiSelectOption[]) => void
): Promise => {
- let availability = { name: '', color: '', mainVisId: '' };
+ let availability = { name: '', color: '', availabilityVisId: '' };
const panelId = application.panelId;
if (!panelId) return availability;
// Fetches saved visualizations associated to application's panel
@@ -252,7 +251,7 @@ export const calculateAvailability = async (
availability = {
name: '',
color: 'null',
- mainVisId: '',
+ availabilityVisId: '',
};
} else {
if (!availabilityFound) {
@@ -263,7 +262,7 @@ export const calculateAvailability = async (
availability = {
name: level.name,
color: level.color,
- mainVisId: visualizationId,
+ availabilityVisId: visualizationId,
};
availabilityFound = true;
}
@@ -273,7 +272,7 @@ export const calculateAvailability = async (
availability = {
name: level.name,
color: level.color,
- mainVisId: visualizationId,
+ availabilityVisId: visualizationId,
};
availabilityFound = true;
}
@@ -283,7 +282,7 @@ export const calculateAvailability = async (
availability = {
name: level.name,
color: level.color,
- mainVisId: visualizationId,
+ availabilityVisId: visualizationId,
};
availabilityFound = true;
}
@@ -293,7 +292,7 @@ export const calculateAvailability = async (
availability = {
name: level.name,
color: level.color,
- mainVisId: visualizationId,
+ availabilityVisId: visualizationId,
};
availabilityFound = true;
}
@@ -303,7 +302,7 @@ export const calculateAvailability = async (
availability = {
name: level.name,
color: level.color,
- mainVisId: visualizationId,
+ availabilityVisId: visualizationId,
};
availabilityFound = true;
}
@@ -313,7 +312,7 @@ export const calculateAvailability = async (
availability = {
name: level.name,
color: level.color,
- mainVisId: visualizationId,
+ availabilityVisId: visualizationId,
};
availabilityFound = true;
}
@@ -328,7 +327,7 @@ export const calculateAvailability = async (
}
setVisWithAvailability(visWithAvailability);
if (!availabilityFound && visWithAvailability.length > 0) {
- return { name: '', color: 'undefined', mainVisId: '' };
+ return { name: '', color: 'undefined', availabilityVisId: '' };
}
return availability;
};
diff --git a/dashboards-observability/public/components/application_analytics/home.tsx b/dashboards-observability/public/components/application_analytics/home.tsx
index 1d837d437..95ab3d978 100644
--- a/dashboards-observability/public/components/application_analytics/home.tsx
+++ b/dashboards-observability/public/components/application_analytics/home.tsx
@@ -24,7 +24,10 @@ import { handleIndicesExistRequest } from '../trace_analytics/requests/request_h
import { ObservabilitySideBar } from '../common/side_nav';
import { NotificationsStart } from '../../../../../src/core/public';
import { APP_ANALYTICS_API_PREFIX } from '../../../common/constants/application_analytics';
-import { ApplicationListType, ApplicationType } from '../../../common/types/app_analytics';
+import {
+ ApplicationRequestType,
+ ApplicationType,
+} from '../../../common/types/application_analytics';
import {
calculateAvailability,
fetchPanelsVizIdList,
@@ -69,7 +72,7 @@ export const Home = (props: HomeProps) => {
} = props;
const [triggerSwitchToEvent, setTriggerSwitchToEvent] = useState(0);
const dispatch = useDispatch();
- const [applicationList, setApplicationList] = useState([]);
+ const [applicationList, setApplicationList] = useState([]);
const [toasts, setToasts] = useState([]);
const [indicesExist, setIndicesExist] = useState(true);
const [appConfigs, setAppConfigs] = useState([]);
@@ -208,10 +211,10 @@ export const Home = (props: HomeProps) => {
.get(`${APP_ANALYTICS_API_PREFIX}/`)
.then(async (res) => {
// Want to calculate availability going down the table
- const mainVisIdStore: Record = {};
+ const availabilityVisIdStore: 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: '', color: 'loading', mainVisId: '' };
+ availabilityVisIdStore[res.data[i].id] = res.data[i].availability.availabilityVisId;
+ res.data[i].availability = { name: '', color: 'loading', availabilityVisId: '' };
}
setApplicationList(res.data);
for (let i = res.data.length - 1; i > -1; i--) {
@@ -219,12 +222,12 @@ export const Home = (props: HomeProps) => {
http,
pplService,
res.data[i],
- mainVisIdStore[res.data[i].id],
+ availabilityVisIdStore[res.data[i].id],
() => {}
);
// Need to set state with new object to trigger re-render
setApplicationList([
- ...res.data.filter((app: ApplicationListType) => app.id !== res.data[i].id),
+ ...res.data.filter((app: ApplicationType) => app.id !== res.data[i].id),
res.data[i],
]);
}
@@ -236,7 +239,7 @@ export const Home = (props: HomeProps) => {
};
// Create a new application
- const createApp = (application: ApplicationType, type: string) => {
+ const createApp = (application: ApplicationRequestType, type: string) => {
const toast = isNameValid(
application.name,
applicationList.map((obj) => obj.name)
@@ -248,7 +251,7 @@ export const Home = (props: HomeProps) => {
const requestBody = {
name: application.name,
- description: application.description,
+ description: application.description || '',
baseQuery: application.baseQuery,
servicesEntities: application.servicesEntities,
traceGroups: application.traceGroups,
@@ -308,7 +311,11 @@ export const Home = (props: HomeProps) => {
};
// Update existing application
- const updateApp = (appId: string, updateAppData: Partial, type: string) => {
+ const updateApp = (
+ appId: string,
+ updateAppData: Partial,
+ type: string
+ ) => {
const requestBody = {
appId,
updateBody: updateAppData,
diff --git a/dashboards-observability/public/components/common/helpers/delete_modal.tsx b/dashboards-observability/public/components/common/helpers/delete_modal.tsx
new file mode 100644
index 000000000..9bf80ec1f
--- /dev/null
+++ b/dashboards-observability/public/components/common/helpers/delete_modal.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useState } from 'react';
+import {
+ EuiOverlayMask,
+ EuiModal,
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFieldText,
+ EuiForm,
+ EuiFormRow,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+
+export const DeleteModal = ({
+ onCancel,
+ onConfirm,
+ title,
+ message,
+}: {
+ onCancel: (
+ event?: React.KeyboardEvent | React.MouseEvent
+ ) => void;
+ onConfirm: (event?: React.MouseEvent) => void;
+ title: string;
+ message: string;
+}) => {
+ const [value, setValue] = useState('');
+ const onChange = (e: React.ChangeEvent) => {
+ setValue(e.target.value);
+ };
+ return (
+
+
+
+ {title}
+
+
+
+ {message}
+ The action cannot be undone.
+
+
+
+ onChange(e)}
+ data-test-subj="popoverModal__deleteTextInput"
+ />
+
+
+
+
+
+ Cancel
+ onConfirm()}
+ color="danger"
+ fill
+ disabled={value !== 'delete'}
+ data-test-subj="popoverModal__deleteButton"
+ >
+ Delete
+
+
+
+
+ );
+};
diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx
index 305445285..a2c7ce8d6 100644
--- a/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx
+++ b/dashboards-observability/public/components/custom_panels/custom_panel_table.tsx
@@ -38,10 +38,11 @@ import {
CUSTOM_PANELS_DOCUMENTATION_URL,
} from '../../../common/constants/custom_panels';
import { UI_DATE_FORMAT } from '../../../common/constants/shared';
-import { getCustomModal, DeletePanelModal } from './helpers/modal_containers';
+import { getCustomModal } from './helpers/modal_containers';
import { CustomPanelListType } from '../../../common/types/custom_panels';
import { getSampleDataModal } from '../common/helpers/add_sample_modal';
import { pageStyles } from '../../../common/constants/shared';
+import { DeleteModal } from '../common/helpers/delete_modal';
/*
* "CustomPanelTable" module, used to view all the saved panels
@@ -177,7 +178,7 @@ export const CustomPanelTable = ({
const deletePanel = () => {
const customPanelString = `operational panel${selectedCustomPanels.length > 1 ? 's' : ''}`;
setModalLayout(
- {
const deletePanel = () => {
setModalLayout(
-
{
configure({ adapter: new Adapter() });
@@ -28,13 +29,13 @@ describe('Modal Container component', () => {
expect(wrapper).toMatchSnapshot();
});
- it('renders DeletePanelModal component', () => {
+ it('renders DeleteModal component', () => {
const onCancel = jest.fn();
const onConfirm = jest.fn();
const title = 'Test Title';
const message = 'Test Message';
const wrapper = shallow(
-
+
);
expect(wrapper).toMatchSnapshot();
});
diff --git a/dashboards-observability/public/components/custom_panels/helpers/modal_containers.tsx b/dashboards-observability/public/components/custom_panels/helpers/modal_containers.tsx
index e403d88bb..0fb80a597 100644
--- a/dashboards-observability/public/components/custom_panels/helpers/modal_containers.tsx
+++ b/dashboards-observability/public/components/custom_panels/helpers/modal_containers.tsx
@@ -105,61 +105,3 @@ export const getDeleteModal = (
);
};
-
-export const DeletePanelModal = ({
- onCancel,
- onConfirm,
- title,
- message,
-}: {
- onCancel: (
- event?: React.KeyboardEvent | React.MouseEvent
- ) => void;
- onConfirm: (event?: React.MouseEvent) => void;
- title: string;
- message: string;
-}) => {
- const [value, setValue] = useState('');
- const onChange = (e: React.ChangeEvent) => {
- setValue(e.target.value);
- };
- return (
-
-
-
- {title}
-
-
-
- {message}
- The action cannot be undone.
-
-
-
- onChange(e)}
- data-test-subj="popoverModal__deleteTextInput"
- />
-
-
-
-
-
- Cancel
- onConfirm()}
- color="danger"
- fill
- disabled={value !== 'delete'}
- data-test-subj="popoverModal__deleteButton"
- >
- Delete
-
-
-
-
- );
-};
diff --git a/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx b/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx
index cc529f862..dc70ba7fd 100644
--- a/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx
+++ b/dashboards-observability/public/components/custom_panels/panel_modules/panel_grid/panel_grid.tsx
@@ -27,6 +27,7 @@ const ResponsiveGridLayout = WidthProvider(Responsive);
* http: http core service;
* chrome: chrome core service;
* panelId: OpenPanel Id
+ * updateAvailabilityVizId: function to update application if availabilityViz is removed from panel
* panelVisualizations: list of panel visualizations
* setPanelVisualizations: function to set panel visualizations
* editMode: boolean to check if the panel is in edit mode
@@ -168,7 +169,7 @@ export const PanelGrid = (props: PanelGridProps) => {
_.omit(layout, ['static', 'moved'])
);
saveVisualizationLayouts(panelId, visualizationParams);
- if (!_.isEmpty(updateAvailabilityVizId)) {
+ if (updateAvailabilityVizId) {
updateAvailabilityVizId(panelVisualizations);
}
}
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
index 1a0f9ade5..49ad1e221 100644
--- 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
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useCallback } from 'react';
+import React, { useCallback, useState } from 'react';
import {
EuiButton,
EuiAccordion,
@@ -17,8 +17,10 @@ import {
EuiFieldText,
EuiSelect,
htmlIdGenerator,
+ EuiText,
} from '@elastic/eui';
import { isEmpty } from 'lodash';
+import { AvailabilityInfoFlyout } from '../../../../../../application_analytics/components/flyout_components/availability_info_flyout';
import { PPL_SPAN_REGEX } from '../../../../../../../../common/constants/shared';
export interface AvailabilityUnitType {
@@ -30,6 +32,8 @@ export interface AvailabilityUnitType {
}
export const ConfigAvailability = ({ visualizations, onConfigChange, vizState = {} }: any) => {
+ const [flyoutOpen, setFlyoutOpen] = useState(false);
+ const closeFlyout = () => setFlyoutOpen(false);
const addButtonText = '+ Add availability level';
const getAvailabilityUnit = () => {
return {
@@ -50,6 +54,13 @@ export const ConfigAvailability = ({ visualizations, onConfigChange, vizState =
{ value: '≠', text: '≠' },
];
+ const availabilityAccordionButton = (
+
+ Availability
+ setFlyoutOpen(true)} size="m" />
+
+ );
+
const hasSpanInApp =
visualizations.data.query.finalQuery.search(PPL_SPAN_REGEX) > 0 &&
visualizations.data.appData.fromApp &&
@@ -105,7 +116,7 @@ export const ConfigAvailability = ({ visualizations, onConfigChange, vizState =
+ {flyoutOpen && }
>
);
};
diff --git a/dashboards-observability/public/components/event_analytics/home/home.tsx b/dashboards-observability/public/components/event_analytics/home/home.tsx
index f25aa4c32..ecd6f79c3 100644
--- a/dashboards-observability/public/components/event_analytics/home/home.tsx
+++ b/dashboards-observability/public/components/event_analytics/home/home.tsx
@@ -30,6 +30,7 @@ import {
EuiText,
EuiHorizontalRule,
} from '@elastic/eui';
+import { DeleteModal } from '../../common/helpers/delete_modal';
import { Search } from '../../common/search/search';
import {
RAW_QUERY,
@@ -55,7 +56,6 @@ import { init as initQueryResult, selectQueryResult } from '../redux/slices/quer
import { SavedQueryTable } from './saved_objects_table';
import { selectQueries } from '../redux/slices/query_slice';
import { setSelectedQueryTab } from '../redux/slices/query_tab_slice';
-import { DeletePanelModal } from '../../custom_panels/helpers/modal_containers';
import { CUSTOM_PANELS_API_PREFIX } from '../../../../common/constants/custom_panels';
import { getSampleDataModal } from '../../common/helpers/add_sample_modal';
import { parseGetSuggestions, onItemSelect } from '../../common/search/autocomplete_logic';
@@ -299,7 +299,7 @@ export const Home = (props: IHomeProps) => {
const deleteHistory = () => {
const customPanelString = `${selectedHistories.length > 1 ? 'histories' : 'history'}`;
setModalLayout(
- => {
+ fetchApps = async (client: ILegacyScopedClusterClient): Promise => {
try {
const response = await client.callAsCurrentUser('observability.getObject', {
objectType: 'application',
});
- return response.observabilityObjectList.map((application: any) => {
- const composition: string[] = application.application.servicesEntities.concat(
- application.application.traceGroups
- );
- const decodedComposition = composition.map((rec) => decodeURI(rec));
+ return response.observabilityObjectList.map((object: any) => {
return {
- name: application.application.name,
- id: application.objectId,
- panelId: application.application.panelId,
- composition: decodedComposition,
+ id: object.objectId,
+ dateCreated: object.createdTimeMs,
+ dateModified: object.lastUpdatedTimeMs,
+ name: object.application.name,
+ description: object.application.description,
+ baseQuery: object.application.baseQuery,
+ servicesEntities: object.application.servicesEntities.map((rec: string) =>
+ decodeURI(rec)
+ ),
+ traceGroups: object.application.traceGroups.map((rec: string) => decodeURI(rec)),
+ panelId: object.application.panelId,
availability: {
name: '',
color: '',
- mainVisId: application.application.availabilityVisId || '',
+ availabilityVisId: object.application.availabilityVisId || '',
},
- dateModified: application.lastUpdatedTimeMs,
- dateCreated: application.createdTimeMs,
};
});
} catch (err: any) {
@@ -38,12 +42,31 @@ export class AppAnalyticsAdaptor {
};
// Fetch application by id
- fetchAppById = async (client: ILegacyScopedClusterClient, appId: string): Promise => {
+ fetchAppById = async (
+ client: ILegacyScopedClusterClient,
+ appId: string
+ ): Promise => {
try {
const response = await client.callAsCurrentUser('observability.getObjectById', {
objectId: appId,
});
- return response.observabilityObjectList[0];
+ const app = response.observabilityObjectList[0];
+ return {
+ id: appId,
+ dateCreated: app.createdTimeMs,
+ dateModified: app.lastUpdatedTimeMs,
+ name: app.application.name,
+ description: app.application.description,
+ baseQuery: app.application.baseQuery,
+ servicesEntities: app.application.servicesEntities.map((rec: string) => decodeURI(rec)),
+ traceGroups: app.application.traceGroups.map((rec: string) => decodeURI(rec)),
+ panelId: app.application.panelId,
+ availability: {
+ name: '',
+ color: '',
+ availabilityVisId: app.application.availabilityVisId || '',
+ },
+ };
} catch (err: any) {
throw new Error('Fetch Application By Id Error: ' + err);
}
@@ -52,22 +75,8 @@ export class AppAnalyticsAdaptor {
// Create a new application
createNewApp = async (
client: ILegacyScopedClusterClient,
- name: string,
- description: string,
- baseQuery: string,
- servicesEntities: string[],
- traceGroups: string[],
- availabilityVisId: string
+ appBody: Partial
) => {
- const appBody = {
- name,
- description,
- baseQuery,
- servicesEntities,
- traceGroups,
- availabilityVisId,
- };
-
try {
const response = await client.callAsCurrentUser('observability.createObject', {
body: {
@@ -102,7 +111,7 @@ export class AppAnalyticsAdaptor {
updateApp = async (
client: ILegacyScopedClusterClient,
appId: string,
- updateAppBody: Partial
+ updateAppBody: Partial
) => {
try {
const response = await client.callAsCurrentUser('observability.updateObjectById', {
diff --git a/dashboards-observability/server/routes/application_analytics/app_analytics_router.ts b/dashboards-observability/server/routes/application_analytics/app_analytics_router.ts
index 21bb8f17b..930e0d5f1 100644
--- a/dashboards-observability/server/routes/application_analytics/app_analytics_router.ts
+++ b/dashboards-observability/server/routes/application_analytics/app_analytics_router.ts
@@ -12,7 +12,7 @@ import {
ILegacyScopedClusterClient,
} from '../../../../../src/core/server';
import { APP_ANALYTICS_API_PREFIX as API_PREFIX } from '../../../common/constants/application_analytics';
-import { ApplicationListType } from '../../../common/types/app_analytics';
+import { ApplicationType } from '../../../common/types/application_analytics';
import { AppAnalyticsAdaptor } from '../../../server/adaptors/application_analytics/app_analytics_adaptor';
export function registerAppAnalyticsRouter(router: IRouter) {
@@ -32,7 +32,7 @@ export function registerAppAnalyticsRouter(router: IRouter) {
const opensearchClient: ILegacyScopedClusterClient = context.observability_plugin.observabilityClient.asScoped(
request
);
- let applicationsData: ApplicationListType[] = [];
+ let applicationsData: ApplicationType[] = [];
try {
applicationsData = await appAnalyticsBackend.fetchApps(opensearchClient);
return response.ok({
@@ -111,15 +111,7 @@ export function registerAppAnalyticsRouter(router: IRouter) {
);
try {
- const newAppId = await appAnalyticsBackend.createNewApp(
- opensearchClient,
- request.body.name,
- request.body.description || '',
- request.body.baseQuery,
- request.body.servicesEntities,
- request.body.traceGroups,
- request.body.availabilityVisId || ''
- );
+ const newAppId = await appAnalyticsBackend.createNewApp(opensearchClient, request.body);
return response.ok({
body: {
message: 'Application Created',
diff --git a/opensearch-observability/.gitignore b/opensearch-observability/.gitignore
index de6e7bf2c..74626e2b8 100644
--- a/opensearch-observability/.gitignore
+++ b/opensearch-observability/.gitignore
@@ -321,3 +321,5 @@ local.properties
local-test
.local-*
+
+/artifacts/
diff --git a/opensearch-observability/build.gradle b/opensearch-observability/build.gradle
index ec32839e7..52d383cd4 100644
--- a/opensearch-observability/build.gradle
+++ b/opensearch-observability/build.gradle
@@ -10,7 +10,7 @@ import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask
buildscript {
ext {
isSnapshot = "true" == System.getProperty("build.snapshot", "true")
- opensearch_version = System.getProperty("opensearch.version", "2.0.1-SNAPSHOT")
+ opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT")
buildVersionQualifier = System.getProperty("build.version_qualifier", "")
version_tokens = opensearch_version.tokenize('-')
opensearch_build = version_tokens[0] + '.0'
@@ -50,6 +50,7 @@ apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'idea'
apply plugin: 'opensearch.opensearchplugin'
+apply plugin: 'opensearch.pluginzip'
apply plugin: 'opensearch.testclusters'
apply plugin: 'io.gitlab.arturbosch.detekt'
apply plugin: 'org.jetbrains.kotlin.jvm'
@@ -66,6 +67,29 @@ opensearchplugin {
classname "org.opensearch.observability.ObservabilityPlugin"
}
+publishing {
+ publications {
+ pluginZip(MavenPublication) { publication ->
+ pom {
+ name = 'opensearch-observability'
+ description = 'OpenSearch Observability plugin'
+ licenses {
+ license {
+ name = 'The Apache License, Version 2.0'
+ url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ }
+ }
+ developers {
+ developer {
+ name = 'OpenSearch'
+ url = 'https://github.com/opensearch-project/observability'
+ }
+ }
+ }
+ }
+ }
+}
+
allOpen {
annotation("org.opensearch.observability.util.OpenForTesting")
}
diff --git a/opensearch-observability/gradle/wrapper/gradle-wrapper.properties b/opensearch-observability/gradle/wrapper/gradle-wrapper.properties
index 011069143..967533df8 100644
--- a/opensearch-observability/gradle/wrapper/gradle-wrapper.properties
+++ b/opensearch-observability/gradle/wrapper/gradle-wrapper.properties
@@ -4,7 +4,7 @@
##
#Wed Jul 29 13:30:55 PDT 2020
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
diff --git a/opensearch-observability/scripts/build.sh b/opensearch-observability/scripts/build.sh
new file mode 100755
index 000000000..4b2893f30
--- /dev/null
+++ b/opensearch-observability/scripts/build.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+#
+# Copyright OpenSearch Contributors
+# SPDX-License-Identifier: Apache-2.0
+#
+
+set -ex
+
+function usage() {
+ echo "Usage: $0 [args]"
+ echo ""
+ echo "Arguments:"
+ echo -e "-v VERSION\t[Required] OpenSearch version."
+ echo -e "-q QUALIFIER\t[Optional] Version qualifier."
+ echo -e "-s SNAPSHOT\t[Optional] Build a snapshot, default is 'false'."
+ echo -e "-p PLATFORM\t[Optional] Platform, ignored."
+ echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored."
+ echo -e "-o OUTPUT\t[Optional] Output path, default is 'artifacts'."
+ echo -e "-h help"
+}
+
+while getopts ":h:v:q:s:o:p:a:" arg; do
+ case $arg in
+ h)
+ usage
+ exit 1
+ ;;
+ v)
+ VERSION=$OPTARG
+ ;;
+ q)
+ QUALIFIER=$OPTARG
+ ;;
+ s)
+ SNAPSHOT=$OPTARG
+ ;;
+ o)
+ OUTPUT=$OPTARG
+ ;;
+ p)
+ PLATFORM=$OPTARG
+ ;;
+ a)
+ ARCHITECTURE=$OPTARG
+ ;;
+ :)
+ echo "Error: -${OPTARG} requires an argument"
+ usage
+ exit 1
+ ;;
+ ?)
+ echo "Invalid option: -${arg}"
+ exit 1
+ ;;
+ esac
+done
+
+if [ -z "$VERSION" ]; then
+ echo "Error: You must specify the OpenSearch version"
+ usage
+ exit 1
+fi
+
+[[ ! -z "$QUALIFIER" ]] && VERSION=$VERSION-$QUALIFIER
+[[ "$SNAPSHOT" == "true" ]] && VERSION=$VERSION-SNAPSHOT
+[ -z "$OUTPUT" ] && OUTPUT=artifacts
+
+mkdir -p $OUTPUT
+
+./gradlew assemble --no-daemon --refresh-dependencies -DskipTests=true -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER
+
+zipPath=$(find . -path \*build/distributions/*.zip)
+distributions="$(dirname "${zipPath}")"
+
+echo "COPY ${distributions}/*.zip"
+mkdir -p $OUTPUT/plugins
+cp ${distributions}/*.zip ./$OUTPUT/plugins
+
+./gradlew publishPluginZipPublicationToZipStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER
+mkdir -p $OUTPUT/maven/org/opensearch
+cp -r ./build/local-staging-repo/org/opensearch/. $OUTPUT/maven/org/opensearch
diff --git a/opensearch-observability/src/test/kotlin/org/opensearch/observability/PluginRestTestCase.kt b/opensearch-observability/src/test/kotlin/org/opensearch/observability/PluginRestTestCase.kt
index 759674eef..c5a1d4115 100644
--- a/opensearch-observability/src/test/kotlin/org/opensearch/observability/PluginRestTestCase.kt
+++ b/opensearch-observability/src/test/kotlin/org/opensearch/observability/PluginRestTestCase.kt
@@ -60,7 +60,7 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() {
open fun wipeAllOpenSearchIndices() {
if (preserveOpenSearchIndicesAfterTest()) return
val response = client().performRequest(Request("GET", "/_cat/indices?format=json&expand_wildcards=all"))
- val xContentType = XContentType.fromMediaTypeOrFormat(response.entity.contentType.value)
+ val xContentType = XContentType.fromMediaType(response.entity.contentType.value)
xContentType.xContent().createParser(
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION,
response.entity.content