Skip to content

Expose merge request timelogs via graphql

Lee Tickett requested to merge expose-mr-timelogs-via-graphql into master

What does this MR do?

Exposes merge request timelogs via graphql

Screenshots (strongly suggested)

Query

query {
  group(fullPath: "twitter") {
    id
    name
    timelogs(startDate: "2021-03-01" endDate: "2021-03-30") { nodes { 
        timeSpent
        issue { id }
        mergeRequest { id }
        note { body } 
    } }
  }
}

Before

{
    "data": {
        "group": {
            "id": "gid://gitlab/Group/27",
            "name": "Twitter",
            "timelogs": {
                "nodes": [
                    {
                        "timeSpent": 600,
                        "issue": {
                            "id": "gid://gitlab/Issue/141"
                        },
                        "mergeRequest": null,
                        "note": {
                            "body": "Another note with some more time"
                        }
                    }
                ]
            }
        }
    }
}

After

{
    "data": {
        "group": {
            "id": "gid://gitlab/Group/27",
            "name": "Twitter",
            "timelogs": {
                "nodes": [
                    {
                        "timeSpent": 600,
                        "issue": {
                            "id": "gid://gitlab/Issue/141"
                        },
                        "mergeRequest": null,
                        "note": {
                            "body": "Another note with some more time"
                        }
                    },
                    {
                        "timeSpent": 300,
                        "issue": null,
                        "mergeRequest": {
                            "id": "gid://gitlab/MergeRequest/29"
                        },
                        "note": {
                            "body": "A note with some time"
                        }
                    }
                ]
            }
        }
    }
}

Plan:

 Limit  (cost=3336.48..3336.52 rows=15 width=52) (actual time=508.997..509.006 rows=0 loops=1)
   Buffers: shared hit=12105 read=1469 dirtied=190
   I/O Timings: read=458.749
   ->  Sort  (cost=3336.48..3336.52 rows=15 width=52) (actual time=508.996..509.004 rows=0 loops=1)
         Sort Key: timelogs.id DESC
         Sort Method: quicksort  Memory: 25kB
         Buffers: shared hit=12105 read=1469 dirtied=190
         I/O Timings: read=458.749
         ->  Merge Join  (cost=2808.77..3336.19 rows=15 width=52) (actual time=508.986..508.994 rows=0 loops=1)
               Merge Cond: (timelogs.project_id = projects.id)
               Buffers: shared hit=12105 read=1469 dirtied=190
               I/O Timings: read=458.749
               ->  Index Scan using index_timelogs_on_project_id_and_spent_at on public.timelogs  (cost=0.43..92692.39 rows=75379 width=52) (actual time=1.921..53.937 rows=10265 loops=1)
                     Index Cond: ((timelogs.spent_at >= '2021-04-01 00:00:00+00'::timestamp with time zone) AND (timelogs.spent_at <= '2021-04-30 22:59:59.999999+00'::timestamp with time zone))
                     Buffers: shared hit=9684 read=428 dirtied=3
                     I/O Timings: read=41.188
               ->  Sort  (cost=2808.11..2817.49 rows=3749 width=4) (actual time=453.420..453.662 rows=1359 loops=1)
                     Sort Key: projects.id
                     Sort Method: quicksort  Memory: 112kB
                     Buffers: shared hit=2421 read=1041 dirtied=187
                     I/O Timings: read=417.561
                     ->  Nested Loop  (cost=1588.90..2585.57 rows=3749 width=4) (actual time=151.310..452.426 rows=1359 loops=1)
                           Buffers: shared hit=2415 read=1041 dirtied=187
                           I/O Timings: read=417.561
                           ->  HashAggregate  (cost=1588.46..1590.37 rows=191 width=4) (actual time=149.035..149.254 rows=228 loops=1)
                                 Group Key: namespaces.id
                                 Buffers: shared hit=754 read=409 dirtied=64
                                 I/O Timings: read=125.499
                                 ->  CTE Scan on base_and_descendants namespaces  (cost=1582.25..1586.07 rows=191 width=4) (actual time=5.966..148.802 rows=228 loops=1)
                                       Buffers: shared hit=754 read=409 dirtied=64
                                       I/O Timings: read=125.499
                                       CTE base_and_descendants
                                         ->  Recursive Union  (cost=0.43..1582.25 rows=191 width=344) (actual time=5.957..148.095 rows=228 loops=1)
                                               Buffers: shared hit=754 read=409 dirtied=64
                                               I/O Timings: read=125.499
                                               ->  Index Scan using index_namespaces_on_type_and_id_partial on public.namespaces namespaces_1  (cost=0.43..3.45 rows=1 width=344) (actual time=5.943..5.944 rows=1 loops=1)
                                                     Index Cond: (((namespaces_1.type)::text = 'Group'::text) AND (namespaces_1.id = 9970))
                                                     Buffers: shared read=4 dirtied=1
                                                     I/O Timings: read=4.665
                                               ->  Nested Loop  (cost=0.56..157.50 rows=19 width=344) (actual time=1.126..23.453 rows=38 loops=6)
                                                     Buffers: shared hit=754 read=405 dirtied=63
                                                     I/O Timings: read=120.834
                                                     ->  WorkTable Scan on base_and_descendants  (cost=0.00..0.20 rows=10 width=4) (actual time=0.001..0.013 rows=38 loops=6)
                                                     ->  Index Scan using index_namespaces_on_parent_id_and_id on public.namespaces namespaces_2  (cost=0.56..15.71 rows=2 width=344) (actual time=0.305..0.615 rows=1 loops=228)
                                                           Index Cond: (namespaces_2.parent_id = base_and_descendants.id)
                                                           Filter: ((namespaces_2.type)::text = 'Group'::text)
                                                           Rows Removed by Filter: 0
                                                           Buffers: shared hit=754 read=405 dirtied=63
                                                           I/O Timings: read=120.834
                           ->  Index Only Scan using index_projects_on_namespace_id_and_id on public.projects  (cost=0.44..5.01 rows=20 width=8) (actual time=0.513..1.326 rows=6 loops=228)
                                 Index Cond: (projects.namespace_id = namespaces.id)
                                 Heap Fetches: 319
                                 Buffers: shared hit=1661 read=632 dirtied=123
                                 I/O Timings: read=292.062

https://postgres.ai/console/gitlab/gitlab-production-tunnel/sessions/3898/commands/13420

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

Security

If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:

  • Label as security and @ mention @gitlab-com/gl-security/appsec
  • The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
  • Security reports checked/validated by a reviewer from the AppSec team
Edited by Lee Tickett

Merge request reports