Skip to content

Expose epic weights sum

charlie ablett requested to merge 5164-backend-cablett-roadmap-epic-bars into master

What does this MR do?

Expose epic weight sum via GraphQL. This includes all issues, including issues not visible to the user (in private subgroups, or confidential).

The lack of filter means that all users can see the same numbers, which is essential for the use case - in this case, planning.

It should be noted that AppSec has approved this.

Screenshots

Does this MR meet the acceptance criteria?

Conformity

Database query analysis

Database queries

SQL query for weight summation

Target epic: &1366

API call for reference: https://gitlab.com/api/v4/groups/9970/epics/1366

Ruby code:

Epics::DescendantWeightService.new(epic, current_user).send(:issues_weight_total)
EXPLAIN
SELECT
    sum(issues.weight) AS sum_weight,
    issues.state_id AS issues_state_id
FROM
    issues
    INNER JOIN projects ON projects.id = issues.project_id
    LEFT JOIN project_features ON projects.id = project_features.project_id
WHERE (EXISTS (
        SELECT
            1
        FROM
            project_authorizations
        WHERE
            project_authorizations.user_id = 1
            AND (project_authorizations.project_id = projects.id))
        OR projects.visibility_level IN (0, 10, 20))
AND (project_features.issues_access_level > 0
    OR project_features.issues_access_level IS NULL)
AND issues.id IN (
    SELECT
        epic_issues.issue_id
    FROM
        epic_issues
    WHERE
        epic_issues.epic_id IN ( WITH RECURSIVE base_and_descendants AS ((
                    SELECT
                        epics.*
                    FROM
                        epics
                    WHERE
                        epics.id = 7651)
                UNION (
                    SELECT
                        epics.*
                    FROM
                        epics,
                        base_and_descendants
                    WHERE
                        epics.parent_id = base_and_descendants.id))
            SELECT
                epics.id
            FROM
                base_and_descendants AS epics
            WHERE
                epics.group_id = 9970))
    AND (weight IS NOT NULL
        AND weight > 0)
GROUP BY
    issues.state_id

Query plan:

content
GroupAggregate  (cost=879.43..879.45 rows=1 width=10)
  Group Key: issues.state_id
  ->  Sort  (cost=879.43..879.43 rows=1 width=6)
        Sort Key: issues.state_id
        ->  Nested Loop Left Join  (cost=846.47..879.42 rows=1 width=6)
              Filter: ((project_features.issues_access_level > 0) OR (project_features.issues_access_level IS NULL))
              ->  Nested Loop  (cost=846.04..878.92 rows=1 width=10)
                    ->  Nested Loop  (cost=845.60..870.78 rows=1 width=10)
                          ->  HashAggregate  (cost=845.04..845.10 rows=6 width=4)
                                Group Key: epic_issues.issue_id
                                ->  Nested Loop  (cost=837.27..845.03 rows=6 width=4)
                                      ->  HashAggregate  (cost=836.98..836.99 rows=1 width=4)
                                            Group Key: epics.id
                                            ->  CTE Scan on base_and_descendants epics  (cost=830.87..836.97 rows=1 width=4)
                                                  Filter: (group_id = 9970)
                                                  CTE base_and_descendants
                                                    ->  Recursive Union  (cost=0.29..830.87 rows=271 width=776)
                                                          ->  Index Scan using epics_pkey on epics epics_1  (cost=0.29..4.30 rows=1 width=776)
                                                                Index Cond: (id = 7651)
                                                          ->  Nested Loop  (cost=0.29..82.11 rows=27 width=776)
                                                                ->  WorkTable Scan on base_and_descendants  (cost=0.00..0.20 rows=10 width=4)
                                                                ->  Index Scan using index_epics_on_parent_id on epics epics_2  (cost=0.29..8.16 rows=3 width=776)
                                                                      Index Cond: (parent_id = base_and_descendants.id)
                                      ->  Index Scan using index_epic_issues_on_epic_id on epic_issues  (cost=0.29..7.98 rows=6 width=8)
                                            Index Cond: (epic_id = epics.id)
                          ->  Index Scan using issues_pkey on issues  (cost=0.56..4.27 rows=1 width=14)
                                Index Cond: (id = epic_issues.issue_id)
                                Filter: ((weight IS NOT NULL) AND (weight > 0))
                    ->  Index Scan using projects_pkey on projects  (cost=0.43..8.13 rows=1 width=4)
                          Index Cond: (id = issues.project_id)
                          Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) OR (visibility_level = ANY ('{0,10,20}'::integer[])))
                          SubPlan 1
                            ->  Index Only Scan using index_project_authorizations_on_user_id_project_id_access_level on project_authorizations  (cost=0.56..4.58 rows=1 width=0)
                                  Index Cond: ((user_id = 1) AND (project_id = projects.id))
                          SubPlan 2
                            ->  Index Only Scan using index_project_authorizations_on_user_id_project_id_access_level on project_authorizations project_authorizations_1  (cost=0.56..7.17 rows=149 width=4)
                                  Index Cond: (user_id = 1)
              ->  Index Scan using index_project_features_on_project_id on project_features  (cost=0.43..0.48 rows=1 width=8)
                    Index Cond: (projects.id = project_id)
content
Aggregate  (cost=879.43..879.45 rows=1 width=10) (actual time=1.065..1.069 rows=2 loops=1)
   Group Key: issues.state_id
   Buffers: shared hit=525
   ->  Sort  (cost=879.43..879.43 rows=1 width=6) (actual time=1.060..1.061 rows=24 loops=1)
         Sort Key: issues.state_id
         Sort Method: quicksort  Memory: 26kB
         Buffers: shared hit=525
         ->  Nested Loop Left Join  (cost=846.47..879.42 rows=1 width=6) (actual time=0.441..1.019 rows=24 loops=1)
               Filter: ((project_features.issues_access_level > 0) OR (project_features.issues_access_level IS NULL))
               Rows Removed by Filter: 0
               Buffers: shared hit=522
               ->  Nested Loop  (cost=846.04..878.92 rows=1 width=10) (actual time=0.424..0.934 rows=24 loops=1)
                     Buffers: shared hit=426
                     ->  Nested Loop  (cost=845.60..870.78 rows=1 width=10) (actual time=0.368..0.742 rows=24 loops=1)
                           Buffers: shared hit=233
                           ->  HashAggregate  (cost=845.04..845.10 rows=6 width=4) (actual time=0.351..0.356 rows=31 loops=1)
                                 Group Key: epic_issues.issue_id
                                 Buffers: shared hit=77
                                 ->  Nested Loop  (cost=837.27..845.03 rows=6 width=4) (actual time=0.258..0.340 rows=31 loops=1)
                                       Buffers: shared hit=77
                                       ->  HashAggregate  (cost=836.98..836.99 rows=1 width=4) (actual time=0.242..0.243 rows=4 loops=1)
                                             Group Key: epics.id
                                             Buffers: shared hit=42
                                             ->  CTE Scan on base_and_descendants epics  (cost=830.87..836.97 rows=1 width=4) (actual time=0.109..0.238 rows=4 loops=1)
                                                   Filter: (epics.group_id = 9970)
                                                   Rows Removed by Filter: 0
                                                   Buffers: shared hit=42
                                                   CTE base_and_descendants
                                                     ->  Recursive Union  (cost=0.29..830.87 rows=271 width=776) (actual time=0.102..0.223 rows=4 loops=1)
                                                           Buffers: shared hit=42
                                                           ->  Index Scan using epics_pkey on public.epics epics_1  (cost=0.29..4.30 rows=1 width=776) (actual time=0.014..0.015 rows=1 loops=1)
                                                                 Index Cond: (epics_1.id = 7651)
                                                                 Buffers: shared hit=3
                                                           ->  Nested Loop  (cost=0.29..82.11 rows=27 width=776) (actual time=0.015..0.021 rows=2 loops=2)
                                                                 Buffers: shared hit=11
                                                                 ->  WorkTable Scan on base_and_descendants  (cost=0.00..0.20 rows=10 width=4) (actual time=0.001..0.001 rows=2 loops=2)
                                                                 ->  Index Scan using index_epics_on_parent_id on public.epics epics_2  (cost=0.29..8.16 rows=3 width=776) (actual time=0.006..0.009 rows=1 loops=4)
                                                                       Index Cond: (epics_2.parent_id = base_and_descendants.id)
                                                                       Buffers: shared hit=11
                                       ->  Index Scan using index_epic_issues_on_epic_id on public.epic_issues  (cost=0.29..7.98 rows=6 width=8) (actual time=0.008..0.023 rows=8 loops=4)
                                             Index Cond: (epic_issues.epic_id = epics.id)
                                             Buffers: shared hit=35
                           ->  Index Scan using issues_pkey on public.issues  (cost=0.56..4.27 rows=1 width=14) (actual time=0.012..0.012 rows=1 loops=31)
                                 Index Cond: (issues.id = epic_issues.issue_id)
                                 Filter: ((issues.weight IS NOT NULL) AND (issues.weight > 0))
                                 Rows Removed by Filter: 0
                                 Buffers: shared hit=156
                     ->  Index Scan using projects_pkey on public.projects  (cost=0.43..8.13 rows=1 width=4) (actual time=0.007..0.007 rows=1 loops=24)
                           Index Cond: (projects.id = issues.project_id)
                           Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) OR (projects.visibility_level = ANY ('{0,10,20}'::integer[])))
                           Rows Removed by Filter: 0
                           Buffers: shared hit=193
                           SubPlan 1
                             ->  Index Only Scan using index_project_authorizations_on_user_id_project_id_access_level on public.project_authorizations  (cost=0.56..4.58 rows=1 width=0) (actual time=0.004..0.004 rows=1 loops=24)
                                   Index Cond: ((project_authorizations.user_id = 1) AND (project_authorizations.project_id = projects.id))
                                   Heap Fetches: 0
                                   Buffers: shared hit=97
                           SubPlan 2
                             ->  Index Only Scan using index_project_authorizations_on_user_id_project_id_access_level on public.project_authorizations project_authorizations_1  (cost=0.56..7.17 rows=149 width=4) (actual time=0.000..0.000 rows=0 loops=0)
                                   Index Cond: (project_authorizations_1.user_id = 1)
                                   Heap Fetches: 0
               ->  Index Scan using index_project_features_on_project_id on public.project_features  (cost=0.43..0.48 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=24)
                     Index Cond: (projects.id = project_features.project_id)
                     Buffers: shared hit=96

Summary:

Time: 8.766 ms
  - planning: 7.352 ms
  - execution: 1.414 ms
    - I/O read: 0.000 ms
    - I/O write: 0.000 ms

Shared buffers:
  - hits: 525 (~4.10 MiB) from the buffer pool
  - reads: 0 from the OS file cache, including disk I/O
  - dirtied: 0
  - writes: 0

FE changes: !18957 (merged)

Related to #5164 (closed)

Edited by 🤖 GitLab Bot 🤖

Merge request reports