diff --git a/services/libs/tinybird/pipes/median_time_to_merge.pipe b/services/libs/tinybird/pipes/median_time_to_merge.pipe new file mode 100644 index 0000000000..c9b0f55bc1 --- /dev/null +++ b/services/libs/tinybird/pipes/median_time_to_merge.pipe @@ -0,0 +1,124 @@ +DESCRIPTION > + - `median_time_to_merge.pipe` serves the "Median time to merge" widget in the Development tab. + - Calculates the median time in seconds from PR opening to merge for the selected time period. + - **When `granularity` is NOT provided, returns a single KPI value** (median) across the entire time range. + - **When `granularity` is provided, returns time-series data** showing median time to merge aggregated by different time periods (daily, weekly, monthly, quarterly, yearly). + - Uses `generate_timeseries` pipe to create consistent time periods and left joins PR data to handle periods with no merged PRs. + - Only includes PRs that have been merged (`isNotNull(pra.mergedAt)`) to ensure accurate merge time calculations. + - Replaces the deprecated `averageTimeToMerge` widget with more robust median-based statistics. + - Available for projects connected with Gerrit, GitHub, or GitLab platforms. + - Primary use case: monitoring code review and merge cycle time, identifying bottlenecks in the merge process. + - Parameters: + - `project`: Required string for project slug (e.g., 'k8s', 'tensorflow') - inherited from `segments_filtered` + - `repos`: Optional array of repository URLs for filtering (e.g., ['https://github.com/kubernetes/kubernetes']) + - `startDate`: Optional DateTime filter for PRs opened after timestamp (e.g., '2024-01-01 00:00:00') + - `endDate`: Optional DateTime filter for PRs opened before timestamp (e.g., '2024-12-31 23:59:59') + - `platform`: Optional string filter for source platform (e.g., 'gerrit', 'github', 'gitlab') + - `granularity`: Optional string for time aggregation ('daily', 'weekly', 'monthly', 'quarterly', 'yearly') + - Response: + - Without granularity: `medianTimeToMergeSeconds` (median value in seconds) + - With granularity: `startDate`, `endDate`, and `medianTimeToMergeSeconds` for each time period + +TAGS "Widget", "Pull requests", "Development metrics", "Merge time" + +NODE timeseries_generation_for_median_time_to_merge +SQL > + % + {% if defined(granularity) %} + SELECT + ds."startDate", + ds."endDate", + ifNull(round(median(prf.mergedInSeconds)), 0) AS "medianTimeToMergeSeconds" + FROM generate_timeseries ds + LEFT JOIN + pull_requests_filtered prf + ON CASE + WHEN {{ granularity }} = 'daily' + THEN toDate(prf.openedAt) + WHEN {{ granularity }} = 'weekly' + THEN toStartOfWeek(prf.openedAt) + WHEN {{ granularity }} = 'monthly' + THEN toStartOfMonth(prf.openedAt) + WHEN {{ granularity }} = 'quarterly' + THEN toStartOfQuarter(prf.openedAt) + WHEN {{ granularity }} = 'yearly' + THEN toStartOfYear(prf.openedAt) + END + = ds."startDate" + AND isNotNull(prf.mergedAt) + AND isNotNull(prf.mergedInSeconds) + {% if defined(platform) %} + AND prf.platform + = {{ + String( + platform, + description="Filter by platform (e.g., 'gerrit', 'github', 'gitlab')", + required=False, + ) + }} + {% end %} + {% if defined(startDate) %} + AND prf.openedAt + >= {{ + DateTime( + startDate, + description="Filter PRs opened after this timestamp", + required=False, + ) + }} + {% end %} + {% if defined(endDate) %} + AND prf.openedAt + <= {{ + DateTime( + endDate, + description="Filter PRs opened before this timestamp", + required=False, + ) + }} + {% end %} + GROUP BY ds."startDate", ds."endDate" + ORDER BY ds."startDate" + {% else %} SELECT 1 + {% end %} + +NODE median_time_to_merge_merged +SQL > + % + {% if not defined(granularity) %} + SELECT round(median(prf.mergedInSeconds)) AS "medianTimeToMergeSeconds" + FROM pull_requests_filtered prf + WHERE + isNotNull(prf.mergedAt) AND isNotNull(prf.mergedInSeconds) + {% if defined(platform) %} + AND prf.platform + = {{ + String( + platform, + description="Filter by platform (e.g., 'gerrit', 'github', 'gitlab')", + required=False, + ) + }} + {% end %} + {% if defined(startDate) %} + AND prf.openedAt + >= {{ + DateTime( + startDate, + description="Filter PRs opened after this timestamp", + required=False, + ) + }} + {% end %} + {% if defined(endDate) %} + AND prf.openedAt + <= {{ + DateTime( + endDate, + description="Filter PRs opened before this timestamp", + required=False, + ) + }} + {% end %} + {% else %} SELECT * FROM timeseries_generation_for_median_time_to_merge + {% end %} diff --git a/services/libs/tinybird/pipes/median_time_to_review.pipe b/services/libs/tinybird/pipes/median_time_to_review.pipe new file mode 100644 index 0000000000..59e4bdc3c7 --- /dev/null +++ b/services/libs/tinybird/pipes/median_time_to_review.pipe @@ -0,0 +1,124 @@ +DESCRIPTION > + - `median_time_to_review.pipe` serves the "Median time to review" widget in the Development tab. + - Calculates the median time in seconds from PR opening to first review for the selected time period. + - **When `granularity` is NOT provided, returns a single KPI value** (median) across the entire time range. + - **When `granularity` is provided, returns time-series data** showing median time to review aggregated by different time periods (daily, weekly, monthly, quarterly, yearly). + - Uses `generate_timeseries` pipe to create consistent time periods and left joins PR data to handle periods with no reviewed PRs. + - Only includes PRs that have been reviewed (`isNotNull(pra.reviewedAt)`) to ensure accurate review time calculations. + - Replaces the deprecated `waitTimeFor1stReview` widget with more robust median-based statistics. + - Available for projects connected with Gerrit, GitHub, or GitLab platforms. + - Primary use case: monitoring code review responsiveness and identifying delays in the review process. + - Parameters: + - `project`: Required string for project slug (e.g., 'k8s', 'tensorflow') - inherited from `segments_filtered` + - `repos`: Optional array of repository URLs for filtering (e.g., ['https://github.com/kubernetes/kubernetes']) + - `startDate`: Optional DateTime filter for PRs opened after timestamp (e.g., '2024-01-01 00:00:00') + - `endDate`: Optional DateTime filter for PRs opened before timestamp (e.g., '2024-12-31 23:59:59') + - `platform`: Optional string filter for source platform (e.g., 'gerrit', 'github', 'gitlab') + - `granularity`: Optional string for time aggregation ('daily', 'weekly', 'monthly', 'quarterly', 'yearly') + - Response: + - Without granularity: `medianTimeToReviewSeconds` (median value in seconds) + - With granularity: `startDate`, `endDate`, and `medianTimeToReviewSeconds` for each time period + +TAGS "Widget", "Pull requests", "Development metrics", "Review time" + +NODE timeseries_generation_for_median_time_to_review +SQL > + % + {% if defined(granularity) %} + SELECT + ds."startDate", + ds."endDate", + ifNull(round(median(prf.reviewedInSeconds)), 0) AS "medianTimeToReviewSeconds" + FROM generate_timeseries ds + LEFT JOIN + pull_requests_filtered prf + ON CASE + WHEN {{ granularity }} = 'daily' + THEN toDate(prf.openedAt) + WHEN {{ granularity }} = 'weekly' + THEN toStartOfWeek(prf.openedAt) + WHEN {{ granularity }} = 'monthly' + THEN toStartOfMonth(prf.openedAt) + WHEN {{ granularity }} = 'quarterly' + THEN toStartOfQuarter(prf.openedAt) + WHEN {{ granularity }} = 'yearly' + THEN toStartOfYear(prf.openedAt) + END + = ds."startDate" + AND isNotNull(prf.reviewedAt) + AND isNotNull(prf.reviewedInSeconds) + {% if defined(platform) %} + AND prf.platform + = {{ + String( + platform, + description="Filter by platform (e.g., 'gerrit', 'github', 'gitlab')", + required=False, + ) + }} + {% end %} + {% if defined(startDate) %} + AND prf.openedAt + >= {{ + DateTime( + startDate, + description="Filter PRs opened after this timestamp", + required=False, + ) + }} + {% end %} + {% if defined(endDate) %} + AND prf.openedAt + <= {{ + DateTime( + endDate, + description="Filter PRs opened before this timestamp", + required=False, + ) + }} + {% end %} + GROUP BY ds."startDate", ds."endDate" + ORDER BY ds."startDate" + {% else %} SELECT 1 + {% end %} + +NODE median_time_to_review_merged +SQL > + % + {% if not defined(granularity) %} + SELECT round(median(prf.reviewedInSeconds)) AS "medianTimeToReviewSeconds" + FROM pull_requests_filtered prf + WHERE + isNotNull(prf.reviewedAt) AND isNotNull(prf.reviewedInSeconds) + {% if defined(platform) %} + AND prf.platform + = {{ + String( + platform, + description="Filter by platform (e.g., 'gerrit', 'github', 'gitlab')", + required=False, + ) + }} + {% end %} + {% if defined(startDate) %} + AND prf.openedAt + >= {{ + DateTime( + startDate, + description="Filter PRs opened after this timestamp", + required=False, + ) + }} + {% end %} + {% if defined(endDate) %} + AND prf.openedAt + <= {{ + DateTime( + endDate, + description="Filter PRs opened before this timestamp", + required=False, + ) + }} + {% end %} + {% else %} SELECT * FROM timeseries_generation_for_median_time_to_review + {% end %} diff --git a/services/libs/tinybird/pipes/patchsets_per_review.pipe b/services/libs/tinybird/pipes/patchsets_per_review.pipe new file mode 100644 index 0000000000..006a3f266f --- /dev/null +++ b/services/libs/tinybird/pipes/patchsets_per_review.pipe @@ -0,0 +1,113 @@ +DESCRIPTION > + - `patchsets_per_review.pipe` serves the "Patchsets per review" widget in the Development tab. + - Calculates median or average number of patchsets per review for Gerrit changesets in the selected time period. + - **When `granularity` is NOT provided, returns a single KPI value** (median or average) across the entire time range. + - **When `granularity` is provided, returns time-series data** showing patchsets per review aggregated by different time periods (daily, weekly, monthly, quarterly, yearly). + - Uses `generate_timeseries` pipe to create consistent time periods and left joins PR data to handle periods with no data. + - Only includes Gerrit changesets with patchset data (`platform = 'gerrit'` and `isNotNull(numberOfPatchsets)`) to ensure accurate calculations. + - Primary use case: analyzing code review iteration patterns and review efficiency specifically for Gerrit changesets. + - Parameters: + - `project`: Required string for project slug (e.g., 'k8s', 'tensorflow') - inherited from `segments_filtered` + - `repos`: Optional array of repository URLs for filtering (e.g., ['https://gerrit.example.com/repo']) + - `startDate`: Optional DateTime filter for changesets opened after timestamp (e.g., '2024-01-01 00:00:00') + - `endDate`: Optional DateTime filter for changesets opened before timestamp (e.g., '2024-12-31 23:59:59') + - `granularity`: Optional string for time aggregation ('daily', 'weekly', 'monthly', 'quarterly', 'yearly') + - `dataType`: Optional string to select calculation method ('median' or 'average'), defaults to 'median' + - Response: + - Without granularity: `patchsetsPerReview` (median or average value) + - With granularity: `startDate`, `endDate`, and `patchsetsPerReview` for each time period + +TAGS "Widget", "Pull requests", "Development metrics", "Code review", "Gerrit" + +NODE timeseries_generation_for_patchsets_per_review +SQL > + % + {% if defined(granularity) %} + {% set dataType_val = 'median' %} + {% if defined(dataType) %} {% set dataType_val = dataType %} {% end %} + SELECT + ds."startDate", + ds."endDate", + {% if dataType_val == 'average' %} + ifNull(round(avg(prf.numberOfPatchsets), 2), 0) AS "patchsetsPerReview" + {% else %} ifNull(round(median(prf.numberOfPatchsets), 2), 0) AS "patchsetsPerReview" + {% end %} + FROM generate_timeseries ds + LEFT JOIN + pull_requests_filtered prf + ON CASE + WHEN {{ granularity }} = 'daily' + THEN toDate(prf.openedAt) + WHEN {{ granularity }} = 'weekly' + THEN toStartOfWeek(prf.openedAt) + WHEN {{ granularity }} = 'monthly' + THEN toStartOfMonth(prf.openedAt) + WHEN {{ granularity }} = 'quarterly' + THEN toStartOfQuarter(prf.openedAt) + WHEN {{ granularity }} = 'yearly' + THEN toStartOfYear(prf.openedAt) + END + = ds."startDate" + AND prf.platform = 'gerrit' + AND isNotNull(prf.numberOfPatchsets) + {% if defined(startDate) %} + AND prf.openedAt + >= {{ + DateTime( + startDate, + description="Filter changesets opened after this timestamp", + required=False, + ) + }} + {% end %} + {% if defined(endDate) %} + AND prf.openedAt + <= {{ + DateTime( + endDate, + description="Filter changesets opened before this timestamp", + required=False, + ) + }} + {% end %} + GROUP BY ds."startDate", ds."endDate" + ORDER BY ds."startDate" + {% else %} SELECT 1 + {% end %} + +NODE patchsets_per_review_merged +SQL > + % + {% if not defined(granularity) %} + {% set dataType_val = 'median' %} + {% if defined(dataType) %} {% set dataType_val = dataType %} {% end %} + SELECT + {% if dataType_val == 'average' %} + round(avg(prf.numberOfPatchsets), 2) AS "patchsetsPerReview" + {% else %} round(median(prf.numberOfPatchsets), 2) AS "patchsetsPerReview" + {% end %} + FROM pull_requests_filtered prf + WHERE + prf.platform = 'gerrit' AND isNotNull(prf.numberOfPatchsets) + {% if defined(startDate) %} + AND prf.openedAt + >= {{ + DateTime( + startDate, + description="Filter changesets opened after this timestamp", + required=False, + ) + }} + {% end %} + {% if defined(endDate) %} + AND prf.openedAt + <= {{ + DateTime( + endDate, + description="Filter changesets opened before this timestamp", + required=False, + ) + }} + {% end %} + {% else %} SELECT * FROM timeseries_generation_for_patchsets_per_review + {% end %} diff --git a/services/libs/tinybird/pipes/review_efficiency.pipe b/services/libs/tinybird/pipes/review_efficiency.pipe index 585437ad6a..a0b31486ae 100644 --- a/services/libs/tinybird/pipes/review_efficiency.pipe +++ b/services/libs/tinybird/pipes/review_efficiency.pipe @@ -17,7 +17,7 @@ DESCRIPTION > - Without granularity: `openedCount` and `mergedCount` (single values) - With granularity: `startDate`, `endDate`, `openedCount`, and `mergedCount` for each time period -TAGS "" Development metrics", Widget", "Pull requests", "Review efficiency" +TAGS "Widget", "Pull requests", "Development metrics", "Review efficiency" NODE review_efficiency_timeseries SQL >