fix(catalog-backend-module-gitlab): fix push event rename heuristic and add API docs

Signed-off-by: Lokesh Kaki <lokeshkaki1@gmail.com>
This commit is contained in:
Lokesh Kaki
2026-03-17 20:00:48 -05:00
parent 1759bfefc8
commit 9af9fc1a85
2 changed files with 45 additions and 61 deletions
@@ -85,7 +85,7 @@ describe('analyzeGitLabWebhookEvent', () => {
`);
});
it('handles file rename as location move', async () => {
it('handles file add and delete in the same commit as separate events', async () => {
const payload = {
object_kind: 'push',
ref: 'refs/heads/main',
@@ -116,9 +116,15 @@ describe('analyzeGitLabWebhookEvent', () => {
"context": {
"commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c3",
},
"fromUrl": "https://gitlab.example.com/group-a/repo-a/-/blob/main/old/catalog-info.yaml",
"toUrl": "https://gitlab.example.com/group-a/repo-a/-/blob/main/new/catalog-info.yaml",
"type": "location.moved",
"type": "location.created",
"url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/new/catalog-info.yaml",
},
{
"context": {
"commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c3",
},
"type": "location.deleted",
"url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/old/catalog-info.yaml",
},
],
"result": "ok",
@@ -21,13 +21,31 @@ import { WebhookPushEventSchema } from '@gitbeaker/rest';
type StringRecord = Record<string, unknown>;
/** @alpha */
/**
* Options for {@link analyzeGitLabWebhookEvent}.
* @alpha
*/
export interface AnalyzeWebhookEventOptions {
/** Optional logger for debug output when events are ignored or unsupported. */
logger?: LoggerService;
/**
* Predicate that returns true for file paths that are relevant to the
* catalog (e.g. paths ending in `.yaml` or `.yml`).
*/
isRelevantPath: (path: string) => boolean;
}
/** @alpha */
/**
* The result of analyzing a GitLab webhook event.
*
* - `ok` — one or more catalog SCM events were produced.
* - `ignored` — the event was valid but not relevant (e.g. push to a
* non-default branch, or no catalog files affected).
* - `aborted` — the event could not be fully processed due to missing data.
* - `unsupported-event` — the event type is not handled by this analyzer.
*
* @alpha
*/
export type AnalyzeWebhookEventResult =
| {
result: 'unsupported-event';
@@ -55,11 +73,6 @@ type PathState =
type: 'removed';
commitUrl?: string;
}
| {
type: 'renamed';
fromPath: string;
commitUrl?: string;
}
| {
type: 'changed';
commitUrl?: string;
@@ -172,13 +185,6 @@ function pathStateToCatalogScmEvent(
url: toBlobUrl(path),
context,
};
case 'renamed':
return {
type: 'location.moved',
fromUrl: toBlobUrl(event.fromPath),
toUrl: toBlobUrl(path),
context,
};
case 'changed':
return {
type: 'location.updated',
@@ -226,13 +232,6 @@ function applyRemovedPath(
pathState.set(path, { type: 'removed', commitUrl });
return;
}
if (previous.type === 'renamed') {
if (!pathState.has(previous.fromPath)) {
pathState.set(previous.fromPath, { type: 'removed', commitUrl });
}
pathState.delete(path);
return;
}
pathState.set(path, previous);
}
@@ -253,34 +252,6 @@ function applyModifiedPath(
pathState.set(path, previous);
}
function applyRenamedPath(
pathState: Map<string, PathState>,
fromPath: string,
toPath: string,
commitUrl: string | undefined,
) {
const previous = pathState.get(fromPath);
pathState.delete(fromPath);
if (!previous) {
pathState.set(toPath, { type: 'renamed', fromPath, commitUrl });
return;
}
if (previous.type === 'added') {
pathState.set(toPath, { type: 'added', commitUrl });
return;
}
if (previous.type === 'renamed') {
pathState.set(toPath, {
type: 'renamed',
fromPath: previous.fromPath,
commitUrl,
});
return;
}
pathState.set(toPath, { type: 'renamed', fromPath, commitUrl });
}
async function onPushEvent(
event: WebhookPushEventSchema,
options: AnalyzeWebhookEventOptions,
@@ -328,16 +299,11 @@ async function onPushEvent(
applyModifiedPath(pathState, path, commitUrl);
}
const renamePairs = Math.min(added.length, removed.length);
for (let i = 0; i < renamePairs; i++) {
applyRenamedPath(pathState, removed[i], added[i], commitUrl);
}
for (const path of added.slice(renamePairs)) {
for (const path of added) {
applyAddedPath(pathState, path, commitUrl);
}
for (const path of removed.slice(renamePairs)) {
for (const path of removed) {
applyRemovedPath(pathState, path, commitUrl);
}
}
@@ -506,7 +472,19 @@ async function onRepositoryUpdateEvent(
};
}
/** @alpha */
/**
* Analyzes a GitLab webhook event and translates it into zero or more catalog
* SCM events that entity providers can act on.
*
* Supported event types:
* - `push` — translates file-level adds, modifications, and deletions on the
* default branch into `location.created`, `location.updated`, and
* `location.deleted` events for paths matching `isRelevantPath`.
* - `repository_update` — translates repository renames, transfers, and
* deletions into `repository.moved` and `repository.deleted` events.
*
* @alpha
*/
export async function analyzeGitLabWebhookEvent(
eventType: string,
eventPayload: unknown,