Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0b5430a
Add workflows for main, prs to main and scan with blackduck and sonar…
lisajulia May 4, 2026
4f2a43b
Add README.md
lisajulia Mar 24, 2026
d0ad47d
Initial version
lisajulia Apr 27, 2026
3067a41
Add artifacts for deployment
lisajulia Apr 25, 2026
6bb6126
Actually provide option to define custom resource group for single
lisajulia Apr 29, 2026
ef6e8a5
Fix name in package.json
lisajulia Apr 29, 2026
f9c0d70
Cleanup: pom, unneeded files
lisajulia Apr 30, 2026
a65799f
Update srv/src/main/java/com/sap/cds/feature/ai/client/setup/AICoreSe…
lisajulia May 1, 2026
99076c1
Update README.md
lisajulia May 1, 2026
8c77bd0
Update REUSE.toml
lisajulia May 1, 2026
88aca3d
Rename AICoreSetup -> AICoreSetupHandler + some cosmetics
lisajulia May 1, 2026
28143ce
Remove .DS_Store
lisajulia May 1, 2026
a36deff
Adjust Readme: aicore handling availabe with first version
lisajulia May 1, 2026
ca15eab
Use the binding and not the environment variable for the integration …
lisajulia May 2, 2026
29dedcc
Add blackduck and codeql action
lisajulia May 6, 2026
edb792a
Version bumps
lisajulia May 12, 2026
0ff1ff0
Migrate codebase from internal fork (multi-module structure) (#18)
Schmarvinius May 22, 2026
b48f76a
chore(hyperspace): 🤖 Add PR Bot Configuration (#15)
hyperspace-insights[bot] May 22, 2026
15c1569
chore: bootstrap ci/cd workflows (#19)
Schmarvinius May 22, 2026
be2c2b3
fix: run npm ci before cds build in integration-tests (#17) (#21)
Schmarvinius May 22, 2026
11e8f96
fix: filter recommendations to unfilled fields only (#22) (#23)
Schmarvinius May 22, 2026
8df01f4
fix(itest): repair resource-group cleanup so quota does not leak (#24…
Schmarvinius May 22, 2026
5f8a3fd
Bump the minor-patch group across 1 directory with 5 updates (#20)
dependabot[bot] May 26, 2026
99999e0
fix(mtx): wire MTX subscribe lifecycle for mock AI Core service (#26)…
Schmarvinius May 27, 2026
5ed1f09
fix: use idiomatic patterns for error messages and stream collection …
Schmarvinius May 27, 2026
45a18eb
refactor: introduce AICoreElements constants; refactor DeploymentHand…
Schmarvinius May 27, 2026
3fe47d2
refactor: slim AICoreService interface to public API surface (#42)
Schmarvinius May 27, 2026
7368c7f
fix: cleanup sonar-* resource groups in CI (#38) (#46)
Schmarvinius May 27, 2026
27b95c3
refactor: decompose FioriRecommendationHandler into focused helpers (…
Schmarvinius May 28, 2026
c189148
refactor: accept SDK API clients as constructor parameters (#44)
Schmarvinius May 28, 2026
24abc83
docs: document RptInferenceClient as intentional public API (#45)
Schmarvinius May 28, 2026
911da7d
fix: resolve empty JaCoCo aggregate report for SonarQube coverage
Schmarvinius May 28, 2026
0f008dd
add reuse (#51)
Schmarvinius May 28, 2026
32c9ee9
fix: add assertion to delete_resourceGroup test case (#50)
Schmarvinius May 28, 2026
24106ff
chore: update repo references from cds-feature-ai to cds-ai (#52)
Schmarvinius May 29, 2026
5a3ba6d
Update Reuse Badge (#53)
Schmarvinius May 29, 2026
40e1f3d
chore(deps): bump the minor-patch group across 1 directory with 2 upd…
dependabot[bot] Jun 1, 2026
060c4ff
chore(deps): bump actions/stale in the minor-patch group (#47)
dependabot[bot] Jun 1, 2026
edb800f
chore: use shared org-level GitHub Actions and workflows (#55)
Schmarvinius Jun 2, 2026
e24fdb9
chore: code review cleanup before review (#57)
Schmarvinius Jun 3, 2026
6bbb6fc
refactor: Migrate API Packages (#69)
Schmarvinius Jun 10, 2026
24aa02c
Comments from #49: Tenant Scoping (#68)
lisajulia Jun 12, 2026
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
8 changes: 8 additions & 0 deletions .cdsprettier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"maxDocCommentLine": 80,
"formatDocComments": true,
"tabSize": 2,
"alignPostAnnotations": false,
"alignColonsInAnnotations": false,
"alignValuesInAnnotations": false
}
7 changes: 7 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Each pull request will require review and approval from the code owners
# before it can be merged.
#
# Learn more: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners

# Global ownership — cdsmunich team owns everything
* @cap-java/cdsmunich
56 changes: 56 additions & 0 deletions .github/actions/cf-bind/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Bind Cloud Foundry Services
description: Login to CF and bind the AI Core service for hybrid testing via cds bind.

inputs:
cf-api:
description: Cloud Foundry API endpoint
required: true
cf-username:
description: Cloud Foundry username
required: true
cf-password:
description: Cloud Foundry password
required: true
cf-org:
description: Cloud Foundry organization
required: true
cf-space:
description: Cloud Foundry space
required: true

runs:
using: composite
steps:
- name: CF Login
uses: cap-java/.github/actions/cf-login@296573b55e906f5c77a1855bcfe4285cbbc5cac4 # main
with:
cf-api: ${{ inputs.cf-api }}
cf-username: ${{ inputs.cf-username }}
cf-password: ${{ inputs.cf-password }}
cf-org: ${{ inputs.cf-org }}
cf-space: ${{ inputs.cf-space }}

- name: Install @sap/cds-dk
shell: bash
run: |
npm i -g @sap/cds-dk@9.9.1
echo "$(npm config get prefix)/bin" >> "${GITHUB_PATH}"

- name: Install CDS dependencies
shell: bash
run: npm ci || npm install
working-directory: integration-tests

- name: Bind ai-core
shell: bash
working-directory: integration-tests
run: |
for i in {1..5}; do
cds bind ai-core -2 ai-core:ai-core-key && break
if [ "$i" -eq 5 ]; then
echo "cds bind ai-core failed after 5 attempts."
exit 1
fi
echo "cds bind ai-core failed, retrying ($i/5)..."
sleep 30
done
44 changes: 44 additions & 0 deletions .github/actions/integration-tests/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Integration Tests
description: Run integration tests using Maven with cds bind for service bindings.

inputs:
java-version:
description: The Java version the build shall run with.
required: true
maven-version:
description: The Maven version the build shall run with.
required: true

runs:
using: composite
steps:
- name: Set up Java ${{ inputs.java-version }}
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
java-version: ${{ inputs.java-version }}
distribution: sapmachine
cache: maven

- name: Setup Maven ${{ inputs.maven-version }}
uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5
with:
maven-version: ${{ inputs.maven-version }}

- name: Build dependencies for integration tests
run: mvn clean install -ntp -B -pl cds-feature-ai-core,cds-feature-recommendations,cds-starter-ai -am -DskipTests
shell: bash

- name: Integration Tests (spring)
env:
CDS_AICORE_TEST_RESOURCE_GROUP: itest-${{ github.run_id }}-${{ github.run_attempt }}-j${{ inputs.java-version }}
run: cds bind --exec -- mvn clean verify -ntp -B -f pom.xml
working-directory: integration-tests
shell: bash

- name: Cleanup AI Core test resource groups
if: always()
working-directory: integration-tests
shell: bash
env:
RESOURCE_GROUP_PREFIX: itest-${{ github.run_id }}-${{ github.run_attempt }}-j${{ inputs.java-version }}
run: cds bind --exec -- node ${{ github.workspace }}/.github/scripts/cleanup-resource-groups.js
91 changes: 91 additions & 0 deletions .github/actions/scan-with-sonar/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Scan with SonarQube
description: Scans the project with SonarQube. Caller is responsible for setting up the CF binding (e.g. via the cf-bind action) before invoking this action.

inputs:
sonarq-token:
description: The token to use for SonarQube authentication
required: true
github-token:
description: The token to use for GitHub authentication
required: true
java-version:
description: The version of Java to use
required: true
maven-version:
description: The version of Maven to use
required: true

runs:
using: composite

steps:
- name: Set up Java ${{inputs.java-version}}
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
java-version: ${{inputs.java-version}}
distribution: sapmachine
cache: maven

- name: Set up Maven ${{inputs.maven-version}}
uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5
with:
maven-version: ${{inputs.maven-version}}

- name: Get Revision
id: get-revision
run: |
echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> "$GITHUB_OUTPUT"
shell: bash

- name: Build and test main modules
run: mvn clean install -ntp -B -pl cds-feature-ai-core,cds-feature-recommendations,cds-starter-ai -am
shell: bash

- name: Run integration tests
env:
CDS_AICORE_TEST_RESOURCE_GROUP: sonar-${{ github.run_id }}-${{ github.run_attempt }}
run: cds bind --exec -- mvn clean verify install -ntp -B
working-directory: integration-tests
shell: bash

- name: Cleanup AI Core test resource groups
if: always()
working-directory: integration-tests
shell: bash
env:
RESOURCE_GROUP_PREFIX: sonar-${{ github.run_id }}-${{ github.run_attempt }}
run: cds bind --exec -- node ${{ github.workspace }}/.github/scripts/cleanup-resource-groups.js

- name: Generate aggregate coverage report
run: mvn verify -ntp -B -pl coverage-report -am -DskipTests
shell: bash

- name: Verify JaCoCo reports exist
run: |
echo "=== Checking JaCoCo reports ==="
find . -name "jacoco.xml" -type f
if [ -f "coverage-report/target/site/jacoco-aggregate/jacoco.xml" ]; then
echo "Found: coverage-report/target/site/jacoco-aggregate/jacoco.xml"
else
echo "Missing: coverage-report/target/site/jacoco-aggregate/jacoco.xml"
exit 1
fi
shell: bash

- name: SonarQube Scan
run: >
mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
-Dsonar.host.url=https://sonar.tools.sap
-Dsonar.token="${SONAR_TOKEN}"
-Dsonar.projectKey=com.sap.cds.cds-ai
-Dsonar.projectVersion=${{ steps.get-revision.outputs.REVISION }}
-Dsonar.qualitygate.wait=true
-Dsonar.java.source=17
-Dsonar.exclusions=**/samples/**,**/integration-tests/**
-Dsonar.coverage.jacoco.xmlReportPaths=${{ github.workspace }}/cds-feature-ai-core/target/site/jacoco/jacoco.xml,${{ github.workspace }}/cds-feature-recommendations/target/site/jacoco/jacoco.xml,${{ github.workspace }}/integration-tests/spring/target/site/jacoco/jacoco.xml,${{ github.workspace }}/coverage-report/target/site/jacoco-aggregate/jacoco.xml
Comment thread
Schmarvinius marked this conversation as resolved.
-Dsonar.coverage.exclusions=**/src/test/**,**/src/gen/**
-B -ntp
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
SONAR_TOKEN: ${{ inputs.sonarq-token }}
30 changes: 30 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: 2
updates:
- package-ecosystem: maven
directories:
- "/"
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
minor-patch:
patterns:
- "*"
update-types:
- minor
- patch

- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
minor-patch:
patterns:
- "*"
update-types:
- minor
- patch
115 changes: 115 additions & 0 deletions .github/scripts/cleanup-resource-groups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Cleans up AI Core test resource groups.
*
* Required environment variables:
* RESOURCE_GROUP_PREFIX - The prefix identifying resource groups owned by this run
* (e.g. "itest-12345-1-j17" or "sonar-12345-1")
*
* Optional environment variables:
* STALE_PREFIXES - Comma-separated list of additional prefixes to clean up
* (defaults to "itest-rg-,cds-itest-")
*
* Credentials are resolved from VCAP_SERVICES (via cds bind) or AICORE_SERVICE_KEY.
*/
const https = require("https");

const DEFAULT_STALE_PREFIXES = ["itest-rg-", "cds-itest-"];

function getCredentials() {
const vcap = JSON.parse(process.env.VCAP_SERVICES || "{}");
return (
(vcap.aicore || vcap["ai-core"] || [{}])[0].credentials ||
JSON.parse(process.env.AICORE_SERVICE_KEY || "null")
);
}

function request(url, opts = {}) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const req = https.request(
{
hostname: u.hostname,
path: u.pathname + u.search,
method: opts.method || "GET",
headers: opts.headers || {},
},
(res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => resolve({ status: res.statusCode, body: data }));
}
);
req.on("error", reject);
if (opts.body) req.write(opts.body);
req.end();
});
}

async function getAccessToken(credentials) {
const tokenUrl = credentials.url + "/oauth/token";
const params = new URLSearchParams({ grant_type: "client_credentials" });
const authHeader =
"Basic " +
Buffer.from(credentials.clientid + ":" + credentials.clientsecret).toString(
"base64"
);
const res = await request(tokenUrl + "?" + params.toString(), {
headers: { Authorization: authHeader },
});
return JSON.parse(res.body).access_token;
}

async function deleteResourceGroups(apiUrl, headers, prefixes) {
const res = await request(apiUrl + "/v2/admin/resourceGroups", { headers });
const groups = JSON.parse(res.body).resources || [];
const toDelete = groups.filter(
(rg) =>
rg.resourceGroupId &&
prefixes.some((p) => rg.resourceGroupId.startsWith(p))
);

for (const rg of toDelete) {
const delRes = await request(
apiUrl + "/v2/admin/resourceGroups/" + rg.resourceGroupId,
{ method: "DELETE", headers }
);
console.log("Delete", rg.resourceGroupId, "->", delRes.status);
}

console.log("Cleaned up", toDelete.length, "resource groups");
}

async function main() {
const ownPrefix = process.env.RESOURCE_GROUP_PREFIX;
if (!ownPrefix) {
console.error("RESOURCE_GROUP_PREFIX environment variable is required");
process.exit(1);
}

const credentials = getCredentials();
if (!credentials) {
console.log("No AI Core credentials found, skipping cleanup");
return;
}

const stalePrefixes = process.env.STALE_PREFIXES
? process.env.STALE_PREFIXES.split(",").map((s) => s.trim())
: DEFAULT_STALE_PREFIXES;

const prefixes = [ownPrefix, ...stalePrefixes];

const apiUrl = credentials.serviceurls.AI_API_URL;
const token = await getAccessToken(credentials);
const headers = {
Authorization: "Bearer " + token,
"AI-Resource-Group": "default",
};

console.log("Cleaning resource groups matching prefixes:", prefixes);
await deleteResourceGroups(apiUrl, headers, prefixes);
}

main().catch((e) => {
console.error(e.message);
process.exit(0);
});
14 changes: 14 additions & 0 deletions .github/workflows/issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Label issues

permissions: {}

on:
issues:
types:
- opened

jobs:
label_issues:
uses: cap-java/.github/.github/workflows/issue.yml@296573b55e906f5c77a1855bcfe4285cbbc5cac4 # main
permissions:
issues: write
Loading
Loading