Skip to content

Conversation

@SumanMaharana
Copy link
Contributor

@SumanMaharana SumanMaharana commented Dec 23, 2025

Describe your changes:

Chore Add Pipeline Obs averageRunTime
In pipelines/name/fqn API we only send the latest taskStatus

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

Summary by Gitar

  • Schema extension:
    • Added averageRunTime field to pipelineObservability.json for tracking average pipeline execution duration in milliseconds
  • New calculation logic:
    • calculateAverageRuntime in PipelineRepository.java computes 30-day average using task-level timing with pipeline-level fallback
  • SQL query optimization:
    • Refactored execution trend queries with CTEs for both MySQL and PostgreSQL to support task-based runtime calculation

This will update automatically on new commits.


@github-actions
Copy link
Contributor

TypeScript types have been updated based on the JSON schema changes in the PR

@github-actions github-actions bot requested a review from a team as a code owner December 23, 2025 15:28
@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

TypeScript types have been updated based on the JSON schema changes in the PR

@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 65%
65.25% (52217/80024) 43.14% (25833/59879) 46.53% (8094/17394)

Comment on lines 425 to +446
"Failed to find pipeline status for %s at %s", pipeline.getName(), timestamp));
}

private Long calculateActualDuration(PipelineStatus pipelineStatus) {
if (pipelineStatus.getTaskStatus() == null || pipelineStatus.getTaskStatus().isEmpty()) {
return null;
}

Long minStartTime =
pipelineStatus.getTaskStatus().stream()
.map(task -> task.getStartTime())
.filter(java.util.Objects::nonNull)
.min(Long::compare)
.orElse(null);

Long maxEndTime =
pipelineStatus.getTaskStatus().stream()
.map(task -> task.getEndTime())
.filter(java.util.Objects::nonNull)
.max(Long::compare)
.orElse(null);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance: Duplicated runtime calculation logic in two methods

Details

The runtime calculation logic (finding min startTime and max endTime from task statuses) is duplicated in three places:

  1. calculateActualDuration() (lines 425-446)
  2. calculateAverageRuntime() (lines 679-748)
  3. The SQL CTEs in CollectionDAO.java

The Java implementations have slightly different behaviors:

  • calculateActualDuration() filters startTime and endTime independently
  • calculateAverageRuntime() requires both startTime AND endTime to be non-null for a task

This inconsistency could lead to different results for the same pipeline data. Consider extracting a shared utility method to ensure consistent calculation across all usages.

Suggested fix: Create a single utility method like calculateRuntimeFromTasks(List<Status> taskStatus) that both methods can use.

Comment on lines +441 to +446
pipelineStatus.getTaskStatus().stream()
.map(task -> task.getEndTime())
.filter(java.util.Objects::nonNull)
.max(Long::compare)
.orElse(null);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Edge Case: calculateActualDuration doesn't fallback to pipeline-level timing

Details

The calculateActualDuration() method returns null when task-level timing data is unavailable, but calculateAverageRuntime() falls back to pipeline-level timing (status.getEndTime() - status.getTimestamp()).

This inconsistency means:

  • Duration filtering will exclude pipeline statuses that only have pipeline-level timing
  • Average runtime calculation will include those same statuses

For consistency, consider adding the same fallback to calculateActualDuration():

if (minStartTime != null && maxEndTime != null && maxEndTime >= minStartTime) {
  return maxEndTime - minStartTime;
}

// Fallback to pipeline-level timing
if (pipelineStatus.getEndTime() != null && pipelineStatus.getTimestamp() != null) {
  return pipelineStatus.getEndTime() - pipelineStatus.getTimestamp();
}

return null;

Comment on lines 425 to +446
"Failed to find pipeline status for %s at %s", pipeline.getName(), timestamp));
}

private Long calculateActualDuration(PipelineStatus pipelineStatus) {
if (pipelineStatus.getTaskStatus() == null || pipelineStatus.getTaskStatus().isEmpty()) {
return null;
}

Long minStartTime =
pipelineStatus.getTaskStatus().stream()
.map(task -> task.getStartTime())
.filter(java.util.Objects::nonNull)
.min(Long::compare)
.orElse(null);

Long maxEndTime =
pipelineStatus.getTaskStatus().stream()
.map(task -> task.getEndTime())
.filter(java.util.Objects::nonNull)
.max(Long::compare)
.orElse(null);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Code Quality: Duplicated runtime calculation logic

Details

calculateActualDuration() and calculateAverageRuntime() both implement the same core logic: finding min(startTime) and max(endTime) from taskStatus. The implementations differ (stream-based vs imperative loop) which makes it harder to maintain consistency.

Consider extracting the common logic into a single shared method:

private Long calculateRuntimeFromTasks(List<Status> taskStatus) {
  if (taskStatus == null || taskStatus.isEmpty()) return null;
  
  Long minStart = taskStatus.stream()
      .map(Status::getStartTime)
      .filter(Objects::nonNull)
      .min(Long::compare)
      .orElse(null);
      
  Long maxEnd = taskStatus.stream()
      .map(Status::getEndTime)
      .filter(Objects::nonNull)
      .max(Long::compare)
      .orElse(null);
      
  return (minStart != null && maxEnd != null && maxEnd >= minStart) 
      ? maxEnd - minStart : null;
}

Both methods can then use this shared helper, reducing duplication and ensuring consistent behavior.

@gitar-bot
Copy link

gitar-bot bot commented Jan 9, 2026

🔍 CI failure analysis for 096965e: Integration tests fail due to double /api prefix in PR code (fix by removing /api). Maven PostgreSQL and SonarCloud CI timed out after 3-4.5 hours. Playwright jobs failed due to infrastructure issues. Retry non-test failures.

Integration Test Failures (Related to PR)

Two CI jobs failed with identical test errors in PipelineResourceIT:

  • integration-tests-postgres-opensearch (job 59924727114)
  • integration-tests-mysql-elasticsearch (job 59924727206)

All three tests fail in both jobs:

  • test_pipelineStatusDurationFilters_200_OK (line 1131)
  • test_pipelineStatusDurationCalculation_200_OK (line 1210)
  • test_pipelineStatusNullDurationHandling_200_OK (line 1263)

Root Cause

The test code constructs API paths that include the /api/v1 prefix, but the HTTP client automatically prepends /api, resulting in a double prefix: /api/api/v1/pipelines/...

Solution

The fix should remove the /api prefix from the path construction in the three failing test methods at lines 1129, 1158, and 1269 in PipelineResourceIT.java, changing from /api/v1/pipelines/%s/status?... to /v1/pipelines/%s/status?...


Infrastructure Failures (Unrelated to PR)

Maven Build Timeouts

  • maven-postgresql-ci (job 59924727529): Timed out after 3 hours (15:21 - 18:17)
  • maven-sonarcloud-ci (job 59924727511): Timed out after 4.5 hours (15:21 - 19:54)

Both jobs show the "Build with Maven" step stuck in in_progress status with no completion time, indicating they exceeded GitHub Actions' job timeout limits and were forcibly terminated. This is an infrastructure/CI timeout issue unrelated to PR changes.

Playwright CI Failures

  • playwright-ci-postgresql (1, 6) (job 59924727119): Failed during Docker build due to network timeout connecting to public.dhe.ibm.com for IBM iAccess ODBC driver
  • playwright-ci-postgresql (3, 6) (job 59924727127): All 454 tests passed but job failed with exit code 1, indicating a workflow infrastructure issue

Solution for Infrastructure Failures

All infrastructure failures (Maven timeouts, Playwright network/workflow issues) should be resolved by retrying the failed jobs.

Code Review ⚠️ Changes requested

The PR adds averageRunTime to pipeline observability with proper implementation, but calculateActualDuration lacks the pipeline-level fallback that other methods have, creating an inconsistency in duration filtering.

⚠️ Bug: calculateActualDuration lacks pipeline-level timing fallback

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:414-438

The calculateActualDuration method (used for duration filtering in getPipelineStatuses()) returns null when task statuses are empty or lack timing data. However, unlike calculateAverageRuntime (which properly falls back to pipeline-level endTime - timestamp), this method doesn't use the pipeline-level timing as a fallback.

This inconsistency means:

  1. Duration filtering will exclude pipelines that have valid pipeline-level timing but no task-level timing
  2. The behavior differs from calculateAverageRuntime and the SQL queries in CollectionDAO, which all implement the fallback

Suggested fix:
Add a fallback to pipeline-level timing at the end of calculateActualDuration:

private Long calculateActualDuration(PipelineStatus pipelineStatus) {
  // ... existing task-level logic ...
  
  if (minStartTime != null && maxEndTime != null && maxEndTime >= minStartTime) {
    return maxEndTime - minStartTime;
  }

  // Fallback to pipeline-level timing
  if (pipelineStatus.getEndTime() != null && pipelineStatus.getTimestamp() != null) {
    return pipelineStatus.getEndTime() - pipelineStatus.getTimestamp();
  }

  return null;
}

Consider extracting shared logic into a utility method to reduce duplication with calculateAverageRuntime.

⚠️ Bug: calculateActualDuration lacks pipeline-level timing fallback

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:408-432

The calculateActualDuration method returns null when task-level timing is unavailable, but it should fall back to pipeline-level timing (endTime - timestamp) for consistency with:\n\n1. calculateAverageRuntime method (lines 523-525) which DOES have this fallback\n2. The SQL queries in CollectionDAO.java which also fall back to pipeline-level timing\n\nImpact: The duration filter in getPipelineStatuses will exclude pipelines that have valid pipeline-level timing but no task-level timing, causing inconsistent behavior.\n\nSuggested fix:\njava\nprivate Long calculateActualDuration(PipelineStatus pipelineStatus) {\n // ... existing task-level logic ...\n \n if (minStartTime != null && maxEndTime != null && maxEndTime >= minStartTime) {\n return maxEndTime - minStartTime;\n }\n\n // Fallback to pipeline-level timing\n if (pipelineStatus.getEndTime() != null && pipelineStatus.getTimestamp() != null) {\n return pipelineStatus.getEndTime() - pipelineStatus.getTimestamp();\n }\n\n return null;\n}\n

⚠️ Bug: calculateActualDuration missing pipeline-level fallback

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:425-446

The calculateActualDuration() method only calculates duration from task-level timing and returns null when taskStatus is empty or lacks timing data. However, calculateAverageRuntime() (lines 679-748) has a fallback to pipeline-level timing (status.getEndTime() - status.getTimestamp()).

This inconsistency causes a bug: duration filtering in getPipelineStatuses() will exclude pipeline runs that have pipeline-level timing but no task-level timing, while the average runtime calculation includes them.

Fix: Add the same fallback to calculateActualDuration():

private Long calculateActualDuration(PipelineStatus pipelineStatus) {
  // ... existing task-level calculation ...
  
  // Fallback to pipeline-level timing
  if (result == null && pipelineStatus.getEndTime() != null && pipelineStatus.getTimestamp() != null) {
    return pipelineStatus.getEndTime() - pipelineStatus.getTimestamp();
  }
  return result;
}
⚠️ Performance: Duplicated runtime calculation logic in two methods

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:425-446 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:679-748

The runtime calculation logic (finding min startTime and max endTime from task statuses) is duplicated in three places:

  1. calculateActualDuration() (lines 425-446)
  2. calculateAverageRuntime() (lines 679-748)
  3. The SQL CTEs in CollectionDAO.java

The Java implementations have slightly different behaviors:

  • calculateActualDuration() filters startTime and endTime independently
  • calculateAverageRuntime() requires both startTime AND endTime to be non-null for a task

This inconsistency could lead to different results for the same pipeline data. Consider extracting a shared utility method to ensure consistent calculation across all usages.

Suggested fix: Create a single utility method like calculateRuntimeFromTasks(List<Status> taskStatus) that both methods can use.

More details 💡 3 suggestions
💡 Code Quality: Duplicated runtime calculation logic across two methods

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:408-432 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:679-748

The runtime calculation logic (finding min startTime and max endTime from tasks) is duplicated in two places:\n\n1. calculateActualDuration (lines 408-432) - uses streams\n2. calculateAverageRuntime (lines 697-720) - uses imperative loops\n\nBoth compute the same thing: max(task.endTime) - min(task.startTime).\n\nImpact: Code duplication increases maintenance burden and risk of bugs when one is updated but not the other (as evidenced by the missing fallback in calculateActualDuration).\n\nSuggested fix: Refactor to use a single helper method:\njava\nprivate Long calculateRuntimeFromStatus(PipelineStatus status) {\n // Calculate from task-level timing first\n if (status.getTaskStatus() != null && !status.getTaskStatus().isEmpty()) {\n // ... common logic ...\n }\n // Fallback to pipeline-level timing\n if (status.getEndTime() != null && status.getTimestamp() != null) {\n return status.getEndTime() - status.getTimestamp();\n }\n return null;\n}\n\nThen reuse in both calculateActualDuration and calculateAverageRuntime.

💡 Code Quality: Duplicated runtime calculation logic

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:425-446 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:679-748

calculateActualDuration() and calculateAverageRuntime() both implement the same core logic: finding min(startTime) and max(endTime) from taskStatus. The implementations differ (stream-based vs imperative loop) which makes it harder to maintain consistency.

Consider extracting the common logic into a single shared method:

private Long calculateRuntimeFromTasks(List<Status> taskStatus) {
  if (taskStatus == null || taskStatus.isEmpty()) return null;
  
  Long minStart = taskStatus.stream()
      .map(Status::getStartTime)
      .filter(Objects::nonNull)
      .min(Long::compare)
      .orElse(null);
      
  Long maxEnd = taskStatus.stream()
      .map(Status::getEndTime)
      .filter(Objects::nonNull)
      .max(Long::compare)
      .orElse(null);
      
  return (minStart != null && maxEnd != null && maxEnd >= minStart) 
      ? maxEnd - minStart : null;
}

Both methods can then use this shared helper, reducing duplication and ensuring consistent behavior.

💡 Edge Case: calculateActualDuration doesn't fallback to pipeline-level timing

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java:441-446

The calculateActualDuration() method returns null when task-level timing data is unavailable, but calculateAverageRuntime() falls back to pipeline-level timing (status.getEndTime() - status.getTimestamp()).

This inconsistency means:

  • Duration filtering will exclude pipeline statuses that only have pipeline-level timing
  • Average runtime calculation will include those same statuses

For consistency, consider adding the same fallback to calculateActualDuration():

if (minStartTime != null && maxEndTime != null && maxEndTime >= minStartTime) {
  return maxEndTime - minStartTime;
}

// Fallback to pipeline-level timing
if (pipelineStatus.getEndTime() != null && pipelineStatus.getTimestamp() != null) {
  return pipelineStatus.getEndTime() - pipelineStatus.getTimestamp();
}

return null;

What Works Well

The calculateAverageRuntime method properly implements task-level timing with pipeline-level fallback. The SQL queries in CollectionDAO are well-structured using CTEs with the same fallback logic. Comprehensive integration tests cover multi-task and null duration scenarios.

Recommendations

Extract the shared runtime calculation logic into a utility method to maintain consistency between calculateActualDuration, calculateAverageRuntime, and the SQL queries. This would prevent future divergence in behavior.

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off Gitar will not commit updates to this branch.
Display: compact Hiding non-applicable rules.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | This comment will update automatically (Docs)

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ingestion safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants