Skip to content

Commit

Permalink
add Insights URL gen to processors & PD connector
Browse files Browse the repository at this point in the history
CamelProcessor tests and db migration not completed yet
  • Loading branch information
jessicarod7 committed Jan 7, 2025
1 parent 8ed9e4a commit bca2353
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class PagerDutyTransformer implements Processor {

public static final String ACCOUNT_ID = "account_id";
public static final String APPLICATION = "application";
public static final String APPLICATION_URL = "application_url";
public static final String BUNDLE = "bundle";
public static final String CLIENT = "client";
public static final String CLIENT_URL = "client_url";
Expand All @@ -36,12 +37,16 @@ public class PagerDutyTransformer implements Processor {
public static final String EVENT_TYPE = "event_type";
public static final String EVENTS = "events";
public static final String GROUP = "group";
public static final String HREF = "href";
public static final String INVENTORY_URL = "inventory_url";
public static final String LINKS = "links";
public static final String ORG_ID = "org_id";
public static final String SEVERITY = "severity";
public static final String SOURCE = "source";
public static final String SOURCE_NAMES = "source_names";
public static final String SUMMARY = "summary";
public static final String TIMESTAMP = "timestamp";
public static final String TEXT = "text";

public static final DateTimeFormatter PD_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+0000");

Expand All @@ -53,6 +58,7 @@ public void process(Exchange exchange) {
JsonObject message = new JsonObject();
message.put(EVENT_ACTION, PagerDutyEventAction.TRIGGER);
message.mergeIn(getClientLink(cloudEventPayload, cloudEventPayload.getString(ENVIRONMENT_URL)));
message.mergeIn(getClientLinks(cloudEventPayload));

JsonObject messagePayload = new JsonObject();
messagePayload.put(SUMMARY, cloudEventPayload.getString(EVENT_TYPE));
Expand Down Expand Up @@ -92,7 +98,9 @@ public void process(Exchange exchange) {
exchange.getIn().setBody(message.encode());
}

/** Validates that the inputs for the required Alert Event fields are present */
/**
* Validates that the inputs for the required Alert Event fields are present
*/
private void validatePayload(final JsonObject cloudEventPayload) {
String summary = cloudEventPayload.getString(EVENT_TYPE);
if (summary == null || summary.isEmpty()) {
Expand Down Expand Up @@ -155,7 +163,34 @@ private JsonObject getClientLink(final JsonObject cloudEventPayload, String envi
return clientLink;
}

private JsonObject getSourceNames(final JsonObject cloudSource) {
/**
* Performs the following link conversions:
* <ul>
* <li>{@link #APPLICATION} integrated into {@link #CLIENT}</li>
* <li>{@link #APPLICATION_URL} becomes {@link #CLIENT_URL}</li>
* <li>{@link #INVENTORY_URL}, if present, creates an entry in the {@link #LINKS} object</li>
* </ul>
* <p>
* The result is similar to the links provided in Microsoft Teams notifications.
*/
static JsonObject getClientLinks(final JsonObject cloudEventPayload) {
JsonObject clientLinks = new JsonObject();

clientLinks.put(CLIENT, String.format("Open %s", cloudEventPayload.getString(APPLICATION)));
clientLinks.put(CLIENT_URL, cloudEventPayload.getString(APPLICATION_URL));

String inventoryUrl = cloudEventPayload.getString(INVENTORY_URL, "");
if (!inventoryUrl.isEmpty()) {
clientLinks.put(LINKS, JsonObject.of(
HREF, inventoryUrl,
TEXT, "Host"
));
}

return clientLinks;
}

static JsonObject getSourceNames(final JsonObject cloudSource) {
if (cloudSource != null) {
JsonObject sourceNames = new JsonObject();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package com.redhat.cloud.notifications.connector.pagerduty;

import com.redhat.cloud.notifications.ingress.Action;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Optional;

import static com.redhat.cloud.notifications.TestConstants.DEFAULT_ACCOUNT_ID;
import static com.redhat.cloud.notifications.TestConstants.DEFAULT_ORG_ID;
import static com.redhat.cloud.notifications.connector.authentication.AuthenticationType.SECRET_TOKEN;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyCloudEventDataExtractor.AUTHENTICATION;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.ACCOUNT_ID;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.APPLICATION;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.APPLICATION_URL;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.BUNDLE;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.CLIENT;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.CLIENT_URL;
Expand All @@ -23,6 +27,7 @@
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.EVENT_ACTION;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.EVENT_TYPE;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.GROUP;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.INVENTORY_URL;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.ORG_ID;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.PAYLOAD;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.PD_DATE_TIME_FORMATTER;
Expand All @@ -31,9 +36,13 @@
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.SOURCE_NAMES;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.SUMMARY;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.TIMESTAMP;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.getClientLinks;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.getSourceNames;

public class PagerDutyTestUtils {

static final String DEFAULT_ENVIRONMENT_URL = "https://console.redhat.com";

static JsonObject createCloudEventData(String url) {
JsonObject authentication = new JsonObject();
authentication.put("type", SECRET_TOKEN.name());
Expand All @@ -56,6 +65,10 @@ static JsonObject createIncomingPayload(JsonObject cloudEventData) {
payload.put(ACCOUNT_ID, DEFAULT_ACCOUNT_ID);
payload.put(APPLICATION, "default-application");
payload.put(BUNDLE, "default-bundle");
payload.put(CONTEXT, JsonObject.of(
DISPLAY_NAME, "console",
"inventory_id", "8a4a4f75-5319-4255-9eb5-1ee5a92efd7f"
));
payload.put(EVENT_TYPE, "default-event-type");
payload.put(EVENTS, JsonArray.of(
JsonObject.of("event-1-key", "event-1-value"),
Expand All @@ -77,6 +90,10 @@ static JsonObject createIncomingPayload(JsonObject cloudEventData) {

payload.put(SOURCE, source);
payload.put(ENVIRONMENT_URL, "https://console.redhat.com");
InsightsUrlsBuilder.buildInventoryUrl(payload)
.ifPresent(url -> payload.put(INVENTORY_URL, url));
InsightsUrlsBuilder.buildApplicationUrl(payload)
.ifPresent(url -> payload.put(APPLICATION_URL, url));
payload.put(SEVERITY, PagerDutySeverity.WARNING);
cloudEventData.put(PAYLOAD, payload);

Expand All @@ -91,6 +108,7 @@ static JsonObject buildExpectedOutgoingPayload(final JsonObject incoming) {
JsonObject oldInnerPayload = expected.getJsonObject(PAYLOAD);
expected.put(EVENT_ACTION, PagerDutyEventAction.TRIGGER);
expected.mergeIn(getClientLink(oldInnerPayload, oldInnerPayload.getString(ENVIRONMENT_URL)));
expected.mergeIn(getClientLinks(oldInnerPayload));

JsonObject newInnerPayload = new JsonObject();
newInnerPayload.put(SUMMARY, oldInnerPayload.getString(EVENT_TYPE));
Expand Down Expand Up @@ -159,29 +177,89 @@ private static JsonObject getClientLink(final JsonObject oldInnerPayload, String

return clientLink;
}
}

private static JsonObject getSourceNames(final JsonObject oldInnerSourceNames) {
if (oldInnerSourceNames != null) {
JsonObject newInnerSourceNames = new JsonObject();
/**
* This class emulates the functionality of the same class within notifications-engine.
*/
class InsightsUrlsBuilder {
/**
* <p>Constructs an Insights URL corresponding to the specific inventory item which generated the notification.</p>
*
* <p>An inventory URL will only be generated if fields from one of these two formats are present:</p>
*
* <ul>
* <li>{@code { "context": { "display_name": "non_empty_string" } }}</li>
* <li>{@code { "context": { "inventory_id": "non_empty_string" }}}</li>
* </ul>
*
* <p>If neither field is present, an {@link Optional#empty()} will be returned. If expected fields of
* {@link Action#getBundle()} or {@link Action#getApplication()} are missing, an inaccurate URL may be returned.</p>
*
* @param data a payload converted by {@code BaseTransformer#toJsonObject(Event)}
* @return URL to the generating inventory item, if required fields are present
*/
static Optional<String> buildInventoryUrl(JsonObject data) {
String path;
ArrayList<String> queryParamParts = new ArrayList<>();

JsonObject application = oldInnerSourceNames.getJsonObject(APPLICATION);
if (application != null) {
newInnerSourceNames.put(APPLICATION, application.getString(DISPLAY_NAME));
}
JsonObject bundle = oldInnerSourceNames.getJsonObject(BUNDLE);
if (bundle != null) {
newInnerSourceNames.put(BUNDLE, bundle.getString(DISPLAY_NAME));
}
JsonObject eventType = oldInnerSourceNames.getJsonObject(EVENT_TYPE);
if (eventType != null) {
newInnerSourceNames.put(EVENT_TYPE, eventType.getString(DISPLAY_NAME));
String displayName = data.getString("display_name", "");
String inventoryId = data.getString("inventory_id", "");

if (!displayName.isEmpty()
&& data.getString("bundle", "").equals("openshift")
&& data.getString("application", "").equals("advisor")) {
path = String.format("/openshift/insights/advisor/clusters/%s", displayName);
} else {
path = "/insights/inventory/";
if (!inventoryId.isEmpty()) {
path += inventoryId;
} else if (!displayName.isEmpty()) {
queryParamParts.add(String.format("hostname_or_id=%s", displayName));
} else {
return Optional.empty();
}
}

if (!newInnerSourceNames.isEmpty()) {
return newInnerSourceNames;
if (!queryParamParts.isEmpty()) {
String queryParams = "?" + String.join("&", queryParamParts);
path += queryParams;
}

return Optional.of(PagerDutyTestUtils.DEFAULT_ENVIRONMENT_URL + path);
}

/**
* <p>Constructs an Insights URL corresponding to the specific inventory item which generated the notification.</p>
*
* <p>If the required field {@link Action#getApplication()} is not present, an
* {@link Optional#empty()} will be returned. If the expected field {@link Action#getBundle()} is not present, an
* inaccurate URL may be returned.</p>
*
* @param data a payload converted by {@code BaseTransformer#toJsonObject(Event)}
* @return URL to the generating application, if required fields are present
*/
static Optional<String> buildApplicationUrl(JsonObject data) {
String path = "";

String bundle = data.getString("bundle", "");
String application;

if (data.containsKey("application") && !data.getString("application", "").isEmpty()) {
application = data.getString("application");
} else {
return Optional.empty();
}

if (bundle.equals("application-services") && application.equals("rhosak")) {
path = "application-services/streams";
} else {
if (bundle.equals("openshift")) {
path = "openshift/";
}
path += "insights/" + application;
}

return null;
return Optional.of(String.format("%s/%s", PagerDutyTestUtils.DEFAULT_ENVIRONMENT_URL, path));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.ENVIRONMENT_URL;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.EVENTS;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.EVENT_TYPE;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.INVENTORY_URL;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.PAYLOAD;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.SEVERITY;
import static com.redhat.cloud.notifications.connector.pagerduty.PagerDutyTransformer.SOURCE;
Expand All @@ -28,7 +29,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/** These test cases are intended to verify that the {@link PagerDutyTransformer} can handle various possible inputs. */
/**
* These test cases are intended to verify that the {@link PagerDutyTransformer} can handle various possible inputs.
*/
@QuarkusTest
@QuarkusTestResource(TestLifecycleManager.class)
public class PagerDutyTransformerTest extends CamelQuarkusTestSupport {
Expand Down Expand Up @@ -83,8 +86,10 @@ void testSuccessfulPayloadTransform() {
validatePayloadTransform(cloudEventData, expectedPayload);
}

/** This is a real test message generated from the {@code /endpoints/{uuid}/test} integrations endpoint, with the
* account and org id replaced. */
/**
* This is a slightly modified version of a real test message generated from the {@code /endpoints/{uuid}/test}
* integrations endpoint, with the account and org id replaced.
*/
@Test
void testSuccessfulTestMessage() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
Expand All @@ -108,6 +113,10 @@ void testSuccessfulTestMessage() {
);
cloudEventPayload.put("recipients", JsonArray.of());
cloudEventPayload.put("environment_url", "https://console.redhat.com");
InsightsUrlsBuilder.buildInventoryUrl(cloudEventPayload)
.ifPresent(url -> cloudEventPayload.put("inventory_url", url));
InsightsUrlsBuilder.buildApplicationUrl(cloudEventPayload)
.ifPresent(url -> cloudEventPayload.put("application_url", url));
cloudEventPayload.put("severity", "warning");
cloudEventData.put(PAYLOAD, cloudEventPayload);

Expand All @@ -130,8 +139,7 @@ void testInvalidTimestampDropped() {
void testMissingSourceNames() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
JsonObject cloudPayload = cloudEventData.getJsonObject(PAYLOAD);
cloudEventData.remove(SOURCE);
cloudEventData.put(PAYLOAD, cloudPayload);
cloudPayload.remove(SOURCE);

JsonObject expectedPayload = buildExpectedOutgoingPayload(cloudEventData);
validatePayloadTransform(cloudEventData, expectedPayload);
Expand All @@ -142,10 +150,7 @@ void testMissingApplicationDisplayName() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
JsonObject cloudPayload = cloudEventData.getJsonObject(PAYLOAD);
JsonObject sourceNames = cloudPayload.getJsonObject(SOURCE);

sourceNames.remove(APPLICATION);
cloudPayload.put(SOURCE, sourceNames);
cloudEventData.put(PAYLOAD, cloudPayload);

JsonObject expectedPayload = buildExpectedOutgoingPayload(cloudEventData);
validatePayloadTransform(cloudEventData, expectedPayload);
Expand All @@ -167,7 +172,6 @@ void testMissingEvents() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
JsonObject cloudPayload = cloudEventData.getJsonObject(PAYLOAD);
cloudPayload.remove(EVENTS);
cloudEventData.put(PAYLOAD, cloudPayload);

JsonObject expectedPayload = buildExpectedOutgoingPayload(cloudEventData);
validatePayloadTransform(cloudEventData, expectedPayload);
Expand All @@ -187,6 +191,17 @@ void testWithClientDisplayName() {
validatePayloadTransform(cloudEventData, expectedPayload);
}

@Test
void testWithMissingClientDisplayName() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
JsonObject cloudPayload = cloudEventData.getJsonObject(PAYLOAD);
JsonObject context = cloudPayload.getJsonObject(CONTEXT);
context.remove(DISPLAY_NAME);

JsonObject expectedPayload = buildExpectedOutgoingPayload(cloudEventData);
validatePayloadTransform(cloudEventData, expectedPayload);
}

@Test
void testWithClientDisplayNameAndInventoryId() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
Expand All @@ -196,6 +211,14 @@ void testWithClientDisplayNameAndInventoryId() {
"inventory_id", "8a4a4f75-5319-4255-9eb5-1ee5a92efd7f"
));
cloudEventData.put(PAYLOAD, cloudPayload);
}

@Test
void testWithMissingInventoryId() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
JsonObject cloudPayload = cloudEventData.getJsonObject(PAYLOAD);
JsonObject context = cloudPayload.getJsonObject(CONTEXT);
context.remove("inventory_id");

JsonObject expectedPayload = buildExpectedOutgoingPayload(cloudEventData);
validatePayloadTransform(cloudEventData, expectedPayload);
Expand All @@ -211,6 +234,16 @@ void testMissingEnvironmentUrl() {
validatePayloadTransform(cloudEventData, expectedPayload);
}

@Test
void testMissingInventoryUrl() {
JsonObject cloudEventData = createIncomingPayload(TEST_URL);
JsonObject cloudPayload = cloudEventData.getJsonObject(PAYLOAD);
cloudPayload.remove(INVENTORY_URL);

JsonObject expectedPayload = buildExpectedOutgoingPayload(cloudEventData);
validatePayloadTransform(cloudEventData, expectedPayload);
}

void verifyTransformExceptionThrown(JsonObject cloudEventData, Class<? extends Throwable> exceptionType, String exceptionMessage) {
Exchange exchange = createExchangeWithBody(context, "I am not used!");

Expand All @@ -219,7 +252,6 @@ void verifyTransformExceptionThrown(JsonObject cloudEventData, Class<? extends T
}

/**
*
* @param cloudEventData the cloud event, as provided to the connector
* @param expectedPayload the PagerDuty payload expected to be sent
*/
Expand Down
Loading

0 comments on commit bca2353

Please sign in to comment.