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
59 changes: 27 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,37 @@
name: Java CI with Maven

on:
pull_request:
types: [opened, synchronize]
push:
branches: [main]
branches: [ "main", "feature/*" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'

- name: Build and Test with Maven
run: mvn clean install

- name: Notify on PR creation
if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }}
run: |
curl -X POST ${{ secrets.CI_BOT }} \
-H 'Content-type: application/json' \
--data "{
\"text\": \"📣 *New Pull Request Created*\n*Repo:* ${{ github.repository }}\n*Author:* ${{ github.actor }}\n*Link:* ${{ github.event.pull_request.html_url }}\"
}"

- name: Notify on Build Failure
if: failure()
run: |
curl -X POST ${{ secrets.CI_BOT }} \
-H 'Content-type: application/json' \
--data "{
\"text\": \"❌ *Build Failed*\n*Repo:* ${{ github.repository }}\n*Actor:* ${{ github.actor }}\n*Workflow:* https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}"
- uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven

- name: Check Formatting
run: mvn spotless:check

- name: Build with Maven
run: mvn -B package --file pom.xml

- name: Run Tests
run: mvn -B test --file pom.xml

- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure() # always run even if the previous step fails
with:
report_paths: '**/target/surefire-reports/TEST-*.xml'
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}
19 changes: 7 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
# 🚀 Java-based CI/CD Notification Bot
# 🚀 AI Developer Assistant Platform

A **centralized, extensible notification microservice** that integrates with GitHub Actions, Jenkins, and other CI/CD systems to keep your team informed proactively and intelligently.
[![Java CI with Maven](https://github.com/Agaba-derrick/devops-notifier/actions/workflows/ci.yml/badge.svg)](https://github.com/Agaba-derrick/devops-notifier/actions/workflows/ci.yml)
An **intelligent, extensible platform** that integrates with GitHub Actions, Jenkins, and other CI/CD systems to assist developers proactively. Powered by Spring AI, it analyzes Pull Requests, explains build failures, and automates workflows.

---

## ✨ **Features**

✅ **AI PR Summarization**
Automatically analyzes Pull Requests using LLMs to provide structured summaries, risk assessments, and key file changes.

✅ **Centralized Notification Engine**
Receive events from multiple CI/CD pipelines and process them through a single service.

✅ **Multi-Channel Support**
Send notifications to Slack, Telegram, Email, Microsoft Teams, and more via pluggable `NotificationSender` classes.

✅ **Configurable Rules Engine**
Define who gets notified about what events, with support for conditions, channels, and teams.

✅ **Scheduled Proactive Notifications**
Run daily checks for stale pull requests, failing builds, or other conditions, and notify responsible developers automatically.

✅ **Notification History and Auditing**
Log all notifications sent, including timestamps, status, and target channel, for audit and debugging.
Send AI-enhanced notifications to Slack, Telegram, Email, Microsoft Teams, and more via pluggable `NotificationSender` classes.

✅ **Extensible Event Processing Architecture**
Add new event processors for Jira, SonarQube, deployment tools, and any custom event source seamlessly.
Expand Down
42 changes: 38 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,29 @@
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.derick</groupId>
<artifactId>ci-alert-bot</artifactId>
<groupId>com.devassistant</groupId>
<artifactId>dev-assistant-platform</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<name>CI Alert Bot</name>
<description>Java-based CI/CD Notification Bot</description>
<name>Developer Assistant Platform</name>
<description>AI-powered Developer Assistant Platform</description>

<properties>
<java.version>21</java.version>
</properties>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
Expand All @@ -38,6 +49,19 @@
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- Spring Boot Starter WebFlux (required for Spring AI) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- Spring AI OpenAI Starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M1</version>
</dependency>

<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
Expand Down Expand Up @@ -82,6 +106,16 @@
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.43.0</version>
<configuration>
<java>
<googleJavaFormat/>
</java>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.devassistant.Notificationbot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* The main entry point for the AI Developer Assistant Platform.
*
* <p>Educational Note: This class uses the {@code @SpringBootApplication} annotation, which is a
* convenience annotation that adds: - {@code @Configuration}: Tags the class as a source of bean
* definitions for the application context. - {@code @EnableAutoConfiguration}: Tells Spring Boot to
* start adding beans based on classpath settings, other beans, and various property settings. -
* {@code @ComponentScan}: Tells Spring to look for other components, configurations, and services
* in the 'com.devassistant' package, allowing it to find the controllers and processors.
*/
@SpringBootApplication
public class NotificationBotApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationBotApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package derrick.Notificationbot.rest;
package com.devassistant.Notificationbot.rest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@GetMapping("/")
public String hello() {
return "Notification Bot is running!";
}
}
@GetMapping("/")
public String hello() {
return "Notification Bot is running!";
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/devassistant/logging/Logger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.devassistant.logging;

public interface Logger {
void info(String message);

void warn(String message);

void error(String message, Throwable t);
}
24 changes: 24 additions & 0 deletions src/main/java/com/devassistant/logging/LoggerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.devassistant.logging;

import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class LoggerImpl implements Logger {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoggerImpl.class);

@Override
public void info(String message) {
log.info(message);
}

@Override
public void warn(String message) {
log.warn(message);
}

@Override
public void error(String message, Throwable t) {
log.error(message, t);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.devassistant.processor;

public class BuildFailureEventProcessor {}
29 changes: 29 additions & 0 deletions src/main/java/com/devassistant/processor/EventProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.devassistant.processor;

/**
* EventProcessor defines the contract for processing different types of CI/CD events.
*
* <p>Educational Note: This interface is the core of the Strategy Design Pattern used in this
* platform. By defining a common interface, the main Webhook controller doesn't need to know the
* specifics of how to handle a Pull Request versus a Build Failure. It can simply iterate through a
* list of EventProcessors, ask each one if it {@code supports()} the event, and if so, call {@code
* processEvent()}. This makes the system highly extensible: to add a new event type, you simply
* create a new class implementing this interface, without modifying the core logic.
*/
public interface EventProcessor {

/**
* Checks if this processor supports the given event type.
*
* @param eventType The event type (e.g. pull_request, build_failure)
* @return true if supported, false otherwise
*/
boolean supports(String eventType);

/**
* Processes the given event payload.
*
* @param payload The event payload as a String (typically JSON)
*/
void processEvent(String payload);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.devassistant.processor;

import com.devassistant.logging.Logger;
import com.devassistant.pullrequest.PullRequestService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

/**
* Processes incoming Pull Request events and utilizes AI to summarize the changes.
*
* <p>Educational Note: This class implements the {@link EventProcessor} interface. We use Spring
* AI's {@link ChatClient} to interact with the configured LLM (e.g., OpenAI). By injecting {@code
* ChatClient.Builder}, we can create a client that sends the PR title and author to the AI to
* generate a risk assessment and summary.
*/
@Component
public class PullRequestEventProcessor implements EventProcessor {

private static final String SUPPORTED_EVENT = "pull_request";

private final ObjectMapper objectMapper;
private final PullRequestService pullRequestService;
private final Logger logger;
private final ChatClient chatClient;

public PullRequestEventProcessor(
PullRequestService pullRequestService, Logger logger, ChatClient.Builder chatClientBuilder) {
this.objectMapper = new ObjectMapper();
this.pullRequestService = pullRequestService;
this.logger = logger;
this.chatClient = chatClientBuilder.build();
}

@Override
public boolean supports(String eventType) {
return SUPPORTED_EVENT.equalsIgnoreCase(eventType);
}

@Override
public void processEvent(String payload) {
try {
JsonNode root = objectMapper.readTree(payload);
JsonNode actionNode = root.path("action");
JsonNode pullRequestNode = root.path("pull_request");

if (actionNode.isMissingNode() || pullRequestNode.isMissingNode()) {
logger.warn("Missing pull request action or data in payload.");
return;
}

String action = actionNode.asText();
String prTitle = pullRequestNode.path("title").asText();
String prUrl = pullRequestNode.path("html_url").asText();
String author = pullRequestNode.path("user").path("login").asText();

// AI Integration: Generate PR Summary
String aiPrompt =
String.format(
"Analyze this PR title and provide a 2 sentence summary and risk assessment (Low/Medium/High). Title: '%s', Author: '%s'",
prTitle, author);

String aiSummary = "AI Summary not available";
try {
aiSummary = chatClient.prompt().user(aiPrompt).call().content();
} catch (Exception aiException) {
logger.error("Failed to generate AI summary for PR", aiException);
}

pullRequestService.handlePullRequest(
action, prTitle + " - AI Analysis: " + aiSummary, prUrl, author);

logger.info(
String.format(
"Processed pull request event: action=%s, title=%s, author=%s",
action, prTitle, author));

} catch (Exception e) {
logger.error("Failed to process pull request event", e);
}
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/devassistant/pullrequest/PullRequestService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.devassistant.pullrequest;

public interface PullRequestService {
void commentOnPullRequest(String payload);

void validatePullRequest(String payload);

void mergePullRequest(String payload);

String fetchPullRequestInfo(String pullRequestId);

void handlePullRequest(String action, String prTitle, String prUrl, String author);
}
Loading
Loading