Skip to content

PPL Alerting 3.3 - #1310

Open
KashKondaka wants to merge 4 commits into
opensearch-project:mainfrom
KashKondaka:ppl-3.3
Open

PPL Alerting 3.3#1310
KashKondaka wants to merge 4 commits into
opensearch-project:mainfrom
KashKondaka:ppl-3.3

Conversation

@KashKondaka

Copy link
Copy Markdown
Collaborator

Description

PPL Alerting UI Changes

Create ppl monitor flyout in discover
v1/v2 coexistence in alerts dashboard and monitors dashboard pages
V2 Trigger graph, monitor details page, create monitor flow, alerts flyout page

Issues Resolved

[List any issues this PR will resolve]

Check List

  • New functionality includes testing.
    • All tests pass
  • New functionality has been documented.
    • New functionality has javadoc added
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

opensearch-trigger-bot Bot and others added 3 commits October 7, 2025 13:03
…ject#1295)

(cherry picked from commit a232821)

Signed-off-by: opensearch-ci <opensearch-infra@amazon.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…opensearch-project#1297)

(cherry picked from commit 147e2e7)

Signed-off-by: Peter Zhu <zhujiaxi@amazon.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Revert "fix mustache format notif message"

This reverts commit 862d635.

update tests

Signed-off-by: KashKondaka <37753523+KashKondaka@users.noreply.github.com>
Comment thread server/services/MonitorService.js Outdated
sortDirection,
search,
severityLevel,
monitorIds,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something new or existing behavior to support both array and a string as possible input?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is existing behavior

}

/** ---------- NEW: generic PPL query passthrough (/_plugins/_ppl) ---------- */
pplQuery = async (context, req, res) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does pplQuery mean? Does it generate it/run it? Let's name it accordingly

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ppl query is the actual query we submit to /_plugins/_ppl api call. We then use the api response later. Maybe proxyPplQuery might be better suited since it is forwarding the api response?

Comment thread server/services/MonitorService.js Outdated
};
/** ------------------------------------------------------------------------ */

listIndices = async (context, req, res) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not doing this in some other service? If so, let's dedupe this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have OpensearchService.getIndices, but that endpoint is a POST that requires an index body parameter and returns the full _cat/indices payload (health/status/etc.). The new UI flow just needs the bare names via a GET so the browser can call it without the extra params. b/c of the shape differences, reusing the existing handler would require another wrapper anyway, so keeping this focused helper avoids coupling the two contracts.

Comment thread server/services/MonitorService.js Outdated
// If the data source cluster doesn't support v2 alerts endpoint, try the default cluster
if (isNoHandlerError(err) && req.query?.dataSourceId) {
try {
console.warn('[alertsForMonitorsV2] Data source cluster does not support v2 alerts, falling back to default cluster');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use logger instead of console.warn so that we can look at those logs later if needed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Comment thread server/services/MonitorService.js Outdated
// If the data source cluster doesn't support v2 alerts endpoint, try the default cluster
if (isNoHandlerError(err) && req.query?.dataSourceId) {
try {
console.warn('[alertsForMonitorsV2] Data source cluster does not support v2 alerts, falling back to default cluster');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we falling back here when the ask is for the V2 alerts and the backend doesn't have it?
We should simply return an error, no?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes will return error, was old code when debugging

Comment thread server/services/MonitorService.js Outdated
const qs = new URLSearchParams();
if (Number.isFinite(Number(ifSeqNo))) qs.append('if_seq_no', String(ifSeqNo));
if (Number.isFinite(Number(ifPrimaryTerm))) qs.append('if_primary_term', String(ifPrimaryTerm));
const path = `/_plugins/_alerting/v2/monitors/${encodeURIComponent(id)}${qs.toString() ? `?${qs}` : ''}`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also be qs.toString()?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I missed that

Comment on lines +286 to +291
const monitor =
_.get(raw, 'monitor_v2.ppl_monitor') ||
_.get(raw, 'ppl_monitor') ||
_.get(raw, 'monitor') ||
_.get(raw, '_source') ||
{};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have so many possibilities with the response? Shouldn't it be defined what to expect?

Comment thread server/services/MonitorService.js Outdated
headers: DEFAULT_HEADERS,
});

console.log("response:", resp);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the console.logs and instead use logger if you need it (like important warnings, errors)

Comment thread server/services/MonitorService.js Outdated
Comment on lines +428 to +443
if (all.length) {
// Filter by state / severity / search (very light client-side filter)
const filtered = all.filter((a) => {
const stateOk = alertState === 'ALL' ? true : String(a.state).toUpperCase() === String(alertState).toUpperCase();
const sevOk = severityLevel === 'ALL' ? true : String(a.severity).toUpperCase() === String(severityLevel).toUpperCase();
const text = `${a.trigger_name ?? ''} ${a.monitor_name ?? ''} ${a.message ?? ''}`.toLowerCase();
const searchOk = !search || text.includes(String(search).toLowerCase());
return stateOk && sevOk && searchOk;
});

// Sort & paginate
const dir = String(sortDirection).toLowerCase() === 'asc' ? 1 : -1;
const key = sortField || 'start_time';
filtered.sort((x, y) => (x[key] === y[key] ? 0 : (x[key] > y[key] ? dir : -dir)));
const totalAlerts = filtered.length;
const page = filtered.slice(Number(from) || 0, (Number(from) || 0) + (Number(size) || 50));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these get handled in the alerting backend APIs? Not scalable here

Comment thread server/services/MonitorService.js Outdated
}
}

// Fallback 1: old v2 endpoint (if it exists)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned why we have fallbacks?

Comment thread server/services/MonitorService.js Outdated
}
};

deleteMonitor = async (context, req, res) => {
try {
const { id } = req.params;
const params = { monitorId: id };
console.log("Deleting monitor id: ", id);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the console.logs

const client = this.getClientBasedOnDataSource(context, req);

// Route to v2 update if payload is v2/PPL
if (isV2MonitorPayload(req.body)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we have a separate update method for ppl monitor, this shouldn't be changed

};

// Get v1 (classic/legacy) monitors only - excludes v2 PPL monitors
getMonitorsV1 = async (context, req, res) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this changing?

Comment thread server/routes/monitors.js
// v1 monitors (classic/legacy monitors)
router.get(
{
path: '/api/alerting/monitors/v1',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we adding new APIs for the existing experience?

Comment thread server/routes/monitors.js
version: schema.number(),
}),
params: schema.object({ id: schema.string() }),
// ⬇️ Same here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant comment?

Comment thread server/routes/monitors.js
version: schema.number(),
}),
params: schema.object({ id: schema.string() }),
// ⬇️ Make version optional so the same route can delete v2 monitors (no version) or legacy (with version).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove now?

@AWSHurneyt

Copy link
Copy Markdown
Collaborator

@KashKondaka could you run the cypress tests locally to make sure they pass, and add the output in this PR as a comment? That should help give us a little more confidence that no existing features were broken by these changes.

Looks like the cypress workflow is failing in this PR because the backend is still pending deployment.

label={
<span>
Use classic monitors{' '}
<EuiToolTip content="Use pre-existing monitor types available in classic alerts.">

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense to say available in classic Alerting?

id="useClassicMonitorsHeader"
label={
<span>
Use classic monitors{' '}

@AWSHurneyt AWSHurneyt Nov 8, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there are multiple Use classic monitors checkboxes in this file. Could you clarify their differences?

monitor_type: MONITOR_TYPE.COMPOSITE_LEVEL,
workflow_type: MONITOR_TYPE.COMPOSITE_LEVEL,
schema_version: 0,
//schema_version: 0,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this still be commented out?

triggers: [],
ui_metadata: {
schedule: uiSchedule,
schedule: formikToUiSchedule(values),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this needed? Looks like uiSchedule is already created by calling formikToUiSchedule(values).

triggers: [],
ui_metadata: {
schedule: uiSchedule,
schedule: formikToUiSchedule(values),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above.

let query = _.get(values, 'query', '');
try {
query = JSON.parse(query);
description = _.get(query, 'description', description);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes below always set description: FORMIK_INITIAL_VALUES.description. Was there a reason to move away from this switch block?

sessionStorage.removeItem(transferKey);
}
} catch (err) {
console.error('[getInitialValues] Failed to load query from sessionStorage:', err);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be bubbled up to the user via a toast?

It would be worthwhile to consider looking at all catch blocks to see if we should add toasts instead of console logs.

* Small service wrapper that calls the server (proxy) API for V2 routes.
* (Preview is handled via /_plugins/_ppl; only create/update live here.)
*/
export const makeAlertingV2Service = (httpClient) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking:
We should consider moving this to a separate services file/directory.

* Build the Monitor V2 (PPL) payload expected by backend.
* Shape: { "ppl_monitor": { name, enabled, schedule, query, triggers, look_back_window_minutes?, timestamp_field? } }
*/
export const buildPPLMonitorFromFormik = (values) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking:
Should move formik conversions to a separate file specific to ppl alerting to help clean up this helpers file.

if (!monitor) return formikValues;
if (!monitorIn) return formikValues;

// Accept v2 wrappers transparently (try both camelCase and snake_case)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify why we need to look for both camelCase and snakeCase?

<div>
<OverviewStat header={'Perform action'} value={'Per monitor execution'} />
<EuiSpacer size={'s'} />
{/* <OverviewStat header={'Perform action'} value={'Per monitor execution'} />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these still be commented out? This would make them unavailable for v1 monitors, wouldn't it?

<EuiCompressedSelect
name={field.name}
value={safeValue}
options={NUMBER_OF_RESULTS_OPERATOR_OPTIONS}

@AWSHurneyt AWSHurneyt Nov 8, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm, NUMBER_OF_RESULTS_OPERATOR_OPTIONS has all of the same entries as THRESHOLD_ENUM_OPTIONS, right? I believe bucket monitors use this too, so I want to make sure those aren't impacted.

this.loadDestinations();
}

// async componentDidMount() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed?


this.setState({ loadingDestinations: true });
try {
const response = await httpClient.get('../api/alerting/destinations', {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify why this was removed? We didn't deprecate the alerting plugin destinations in 3.0 to my knowledge.

{isBucketLevelMonitor ? (
<FieldArray name={'triggerConditions'} validateOnChange={true}>
{(arrayHelpers) => (
<DefineBucketLevelTrigger

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see DefineBucketLevelTrigger in the DefineTriggerV1 file. Does this get used elsewhere now?

subject_template: {
lang: 'mustache',
source: 'Monitor {{ctx.monitor.name}} triggered an alert {{ctx.trigger.name}}',
source: 'Monitor {{ctx.monitorV2.name}} triggered an alert {{ctx.trigger.name}}',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm this template was supposed to be changed? The file path suggests this is a composite monitor asset, and composite monitors wouldn't use v2 naming patterns, right?

{{#ctx.completedAlerts}}
* {{id}} : {{bucket_keys}}
{{/ctx.completedAlerts}}
Monitor {{ctx.monitorV2.name}} just entered alert status. Please investigate the issue.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were these templates intended to be changed? Looks like they're associated with query and bucket monitors.

{ value: 'ALL', text: 'All alerts' },
{ value: ALERT_STATE.ACTIVE, text: 'Active' },
{ value: ALERT_STATE.ACKNOWLEDGED, text: 'Acknowledged' },
{ value: ALERT_STATE.COMPLETED, text: 'Completed' },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing these entries completely would impact v1 monitors, wouldn't it?

totalAlerts: 0,
totalTriggers: 0,
chainedAlert: undefined,
//chainedAlert: undefined,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intended to be commented out still? Would this impact composite monitors?

const { httpClient, history, notifications, perAlertView } = this.props;
history.replace({ ...this.props.location, search: queryParamsString });
const extendedParams = {
...(dataSourceId !== undefined && { dataSourceId }), // Only include dataSourceId if it exists

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that params gets used a little further down in the apiParams variable; but it doesn't look like dataSourceId doesn't seem to get used. Is it no longer needed?

size: monitorIds.length || 1000,
};

const response = await httpClient.post('../api/alerting/monitors/_search', {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v2 endpoint doesn't return v1 monitors, right? Should there be a condition here to determine which endpoint to use?

openChainedAlertsFlyout = (chainedAlert) => {
this.setState({ chainedAlert });
};
// openChainedAlertsFlyout = (chainedAlert) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sections that are commented out are needed for composite monitors aren't they?

alertsByTriggers,
alertState,
chainedAlert,
//chainedAlert,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question; isn't this needed for composite monitors?

},
],
});
// - columns.push({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question; isn't this needed for composite monitors?

return (
<>
{chainedAlert && (
{/* {chainedAlert && (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be re-enabled in classic mode?

monitorType={monitorType}
alertActions={useUpdatedUx ? actions() : undefined}
panelStyles={{ padding: perAlertView ? '8px 0px 16px' : '0px 0px 16px' }}
alertActions={null}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this only be null in non-classic mode?

// },
// {
// field: 'acknowledged_time',
// name: 'Time acknowledged',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be added for classic mode?

const dataSources = getDataSources(monitor, localClusterName);

const overviewStats = [
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want these sections for classic mode?

Comment thread public/plugin.tsx
public setup(core: CoreSetup<AlertingStartDeps, AlertingStart>, { expressions, uiActions, dataSourceManagement, dataSource, assistantDashboards }: AlertingSetupDeps) {
public setup(core: CoreSetup<AlertingStartDeps, AlertingStart>, { expressions, uiActions, dataSourceManagement, dataSource, assistantDashboards, explore }: AlertingSetupDeps) {

// const mountWrapper = async (params: AppMountParameters, redirect: string) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few commented out sections in this file. Are they still needed?

Comment thread public/utils/helpers.js
} else if (errorMessage) {
messageText = String(errorMessage);
} else {
messageText = 'An unknown error occurred';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking:
An error message should end with a full-stop (i.e., .).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants