Skip to content

fix: enable GitLab productivity rate metrics#19

Open
agzyamov wants to merge 27 commits into
epam:mainfrom
agzyamov:feature/gitlab-rate-metrics
Open

fix: enable GitLab productivity rate metrics#19
agzyamov wants to merge 27 commits into
epam:mainfrom
agzyamov:feature/gitlab-rate-metrics

Conversation

@agzyamov
Copy link
Copy Markdown

Summary

This PR fixes 5 bugs in the GitLab client that silently prevented all commit and approval data from appearing in productivity reports. Teams using GitLab instead of GitHub can now get meaningful rate metrics.

Bugs Fixed

1. GitLabCommit.getAuthor() — wrong JSON shape

GitLab commits API returns author_name/author_email as flat top-level strings, not a nested author object. The old code tried getJSONObject("author") and always threw/returned null. Fixed by building a synthetic GitLabUser from the flat fields.

2. GitLabCommit.getCommiterTimestamp() — wrong field + wrong date parser

GitLab uses committed_date (ISO 8601 with milliseconds) at the top level. The old code tried getJSONObject("committer").getString("date") which always threw JSONException. Fixed: use getString("committed_date") with DateUtils.smartParseDate() (not parseIsoDate, which doesn't handle the milliseconds variant).

3. GitLabCommit.getUrl() — hardcoded stub

The method returned the literal string "url is not supported" instead of reading the field. Fixed: getString("web_url").

4. GitLab.getCommitsFromBranch() — only first 20 commits, no date filter

The old implementation fetched a single page of 20 commits with no date range, so the 90-day productivity window always showed ≤ 20 commits. Fixed: added per_page=100, since= ISO-date param, and a pagination loop.

5. GitLab.pullRequestActivities() — heuristic note-matching instead of Approvals API

The old code scanned MR notes for the word "approved" — fragile, language-dependent, and wrong for system notes. Fixed: call the real GitLab Approvals API (GET /projects/:id/merge_requests/:iid/approvals), parse the approved_by[] array. A new GitLabMRApproval model wraps each entry.

New File

  • gitlab/model/GitLabMRApproval.java — models the { "user": {...} } shape from the Approvals API response.

Tests

All existing tests pass. New tests added for:

  • GitLabCommitTest — corrected JSON fixture + 4 new tests (getAuthor, getCommiterTimestamp, getUrl, correct field names)
  • GitLabTest — replaced heuristic-approval test with 4 tests covering: pagination loop, since-filter param, Approvals API call, approved-by extraction
  • GitLabMRApprovalTest — 3 tests for the new model
./gradlew :dmtools-core:test  →  BUILD SUCCESSFUL (86 tests)

References

agzyamov and others added 23 commits April 8, 2026 18:21
Add a reusable skill that keeps Confluence documentation pages in sync
with agent configuration changes. The skill:

- Detects changes to agent configs, JS actions, prompts, instructions,
  and CI/CD pipelines via configurable watch paths
- Guides the AI through a fetch-update-publish workflow using DMtools
  Confluence commands
- Supports configurable Confluence target (space, page title, page ID)
  with auto-discovery from dmtools.env
- Provides a generalized page structure template (Common Configuration +
  Project-Specific Configuration table)
- Handles edge cases: new page creation, missing files, shared actions

Also adds a reference to the new skill in the main SKILL.md
documentation table.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The file had the additionalInstructions content duplicated after the
closing `};` of module.exports, causing a parse error in the GraalVM
JS engine and breaking the SM agent with 'Missing owner or repo'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 17 new @MCPTool-annotated PR methods to AzureDevOpsClient:
  ado_list_prs, ado_get_pr, ado_get_pr_comments, ado_add_pr_comment,
  ado_add_inline_comment, ado_reply_to_pr_thread, ado_resolve_pr_thread,
  ado_update_pr_comment, ado_delete_pr_comment, ado_get_pr_diff,
  ado_merge_pr, ado_update_pr, ado_add_pr_reviewer, ado_remove_pr_reviewer,
  ado_add_pr_label, ado_remove_pr_label, ado_get_pr_work_items

- All tools include source_code_* aliases for cross-platform compatibility
- Add ADO to IntegrationType enum
- Fix patch() Content-Type handling for Git API vs Work Item API
- Add integration tests for all PR tools (tests against RustemAgziamov ADO org)
- Update dmtools-ai-docs: ado-tools.md (14 → 31 tools), ado.md config guide,
  SKILL.md, mcp-tools/README.md with correct tool counts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ADO client was missing from the MCP CLI client registry, causing all
ado_* tools to fail with 'client is null' when invoked via dmtools.sh.
Added BasicAzureDevOpsClient.getInstance() registration alongside other
integrations (Jira, GitHub, GitLab, etc.).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eUris

Jira and Confluence embed GitHub links in smart-link format:
  [[https://github.com/.../file.md|https://github.com/.../file.md|smart-link]]

The parseUris regex lookahead did not include '|' as a terminator, so
the entire '|display-text|smart-link]' suffix was included in the
matched URL. This caused GitHub.getFileContentFromGithubWebUrl() to
make an API call with a malformed path that always returned 404.

Fixes:
1. parseUris: add '|', '[', ']' to the regex lookahead terminator set
   so the match stops at the first pipe character.
2. uriToObject: strip any '|...' suffix as a defensive second layer
   before calling getFileContent().

Adds unit tests covering plain URLs, single/multiple smart-link URLs,
empty and null inputs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Automatically creates a pre-release GitHub Release on every push to main.

- Version: {current_version}-beta.{run_number} (e.g. 1.7.173-beta.42)
- Runs the same build/test/package pipeline as the regular release
- Packages CLI JAR + install scripts and AI skill archives
- Published with prerelease: true so the stable 'latest' release is
  never displaced by a beta build
- Can also be triggered manually via workflow_dispatch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use least-privilege top-level permissions (contents: read); elevated
  grants remain on specific jobs that need them
- Append github.run_attempt to beta version suffix to prevent tag
  collisions on workflow re-runs (e.g. 1.7.173-beta.42.1)
- Quote $GITHUB_OUTPUT redirects for robustness
- Improve zip/tar exclude patterns to handle nested node_modules/.git
- Replace pipe-to-shell install instructions with download-then-execute
  pattern to reduce supply-chain risk

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The build-and-test reusable workflow requests 'checks: write' and
'packages: write' on its nested job. A called workflow cannot exceed
the permissions granted by the caller, so these must be present at the
top-level permission block.

contents: read remains the default; only create-beta-release elevates
it to write via its job-level override.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move checks:write and packages:write from the top-level permissions
block to the build-and-test job that actually needs them. Top-level
now grants only contents:read, so every other job runs with minimal
privileges.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix four bugs that silently suppressed all GitLab commit and approval
data in the productivity reporting pipeline:

1. GitLabCommit.getAuthor() — GitLab commits API returns author_name /
   author_email as flat top-level strings, not a nested 'author' object.
   Build a synthetic GitLabUser from those fields instead.

2. GitLabCommit.getCommiterTimestamp() — Use top-level 'committed_date'
   (ISO 8601) via DateUtils.smartParseDate() instead of the non-existent
   nested 'committer.date' path that caused JSONException at runtime.

3. GitLabCommit.getUrl() — Return 'web_url' from the response instead
   of the hardcoded placeholder string.

4. GitLab.getCommitsFromBranch() — Add 'since' date filter, per_page=100,
   and a page loop so all commits in the requested window are fetched
   (previously fetched only the first 20 with no date filtering).

5. GitLab.pullRequestActivities() — Replace heuristic text-matching with
   a real call to the GitLab MR Approvals API
   (GET /projects/:id/merge_requests/:iid/approvals). Each entry in
   'approved_by' becomes an IActivity with action=APPROVED and the
   actual approver user set. Discussion notes are still included as
   COMMENTED activities.

6. New model: GitLabMRApproval wrapping the 'approved_by' entry shape
   { "user": { ... } } returned by the Approvals API.

Tests updated / added:
- GitLabCommitTest: fix old wrong-shape JSON in testGetAuthor; add tests
  for getAuthor() flat-field parsing, null-guard, getCommiterTimestamp(),
  getUrl().
- GitLabMRApprovalTest: new test class for the new model.
- GitLabTest: replace old heuristic-approval test with two new tests
  covering the real Approvals API path; add tests for
  getCommitsFromBranch() pagination and null-response handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@agzyamov agzyamov force-pushed the feature/gitlab-rate-metrics branch from db93f99 to 15ff3b0 Compare April 18, 2026 06:35
Instead of fetching all MR pages and stopping when we hit an old one,
pass created_after=startDate directly to the API. GitLab filters
server-side — only MRs in the reporting window are returned.

For INS with 36 repos × 150 avg MRs, this reduces API calls from
~10,800 to only MRs created in the last 3 months (~hundreds).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@agzyamov agzyamov force-pushed the feature/gitlab-rate-metrics branch from 365c2f0 to ad883c0 Compare April 18, 2026 15:31
agzyamov and others added 3 commits April 18, 2026 18:44
MetricFactory was constructed once with the global SourceCode (GitHub
default), so all SourceCollectors (PullRequestsMetricSource etc.) always
called api.github.com regardless of sourceType in the datasource config.

Fix:
- DataSourceFactory.resolveSourceCode() made public
- MetricFactory.createMetric() overload accepts resolvedSourceCode
- createSourceCollector() overload uses resolvedSourceCode when provided
- ReportGenerator.collectDataFromAllSources() resolves sourceCode per
  datasource and passes it to both DataSourceFactory and MetricFactory

Verified locally: CON report now calls gitlab.com exclusively.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With sort=asc (oldest first), page 1..N-1 contain stable old MRs that
never change between daily runs → permanent HTTP cache hits.
Only the last page (newest MRs) changes daily → single cache miss per repo.
Combined with DMTOOLS_CACHE_ENABLED=true this gives true delta fetching.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- getCommitsFromBranch: add with_stats=true so each commit carries
  additions/deletions inline (no extra per-commit API calls)
- GitLabCommit.getStats(): implement IDiffStats.getStats() reading
  the 'stats' JSON field returned by GitLab commits API
- PullRequestsLOCMetricSource: use inline stats when commit already
  implements IDiffStats (avoids N getCommitDiffStat API calls);
  fall back to getCommitDiffStat only when stats are not inline
- PullRequestsLinesOfCodeMetricSource: read inline IDiffStats weight
  (defensive fix for the old implementation path)

Result: Lines Of Code (K) went from 0 to ~220K for CON project.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants