Skip to content
Snippets Groups Projects

Add a Tag Name filter to the Incident Timeline API

What does this MR do and why?

Adds a 'Tag Name' filter to the Incident Timeline GraphQL API.

Query plan: !109293 (comment 1325355202)

Issue: #360009 (closed)

Screenshots or screen recordings

No visual changes.

How to set up and validate locally

Numbered steps to set up and validate the change are strongly suggested.

Set up data:

  1. Navigate to -> Monitor -> Incidents
  2. Create or edit an incident.
  3. Navigate to the Timeline tab.
  4. Add a few timeline items, including one with the tag 'Start Time'.

Query the API:

  1. Navigate to the graphql explorer
  2. Paste in the following query and data:
    • Click to expand Query
      query timelineQueryWithTagFilter($fullPath: ID!, $incidentId: IssueID!, $tagName: String) {
        project(fullPath: $fullPath) {
          id
          incidentManagementTimelineEvents(incidentId: $incidentId, tagName: $tagName) {
            nodes {
              id
              note
              timelineEventTags {
                nodes {
                  id
                  name
                }
              }
            }
          }
        }
      }
    • Click to expand Query Variables
      {
        "fullPath": "flightjs/Flight (Project  ID)",
        "incidentId": "gid://gitlab/Issue/<issue id",
        "tagName": "Start time"
      }
  3. Ensure the result includes the 'Start Time' incident timeline event.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #360009 (closed)

Edited by Tristan Read

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
  • Tristan Read added 1 commit

    added 1 commit

    Compare with previous version

  • Tristan Read added 258 commits

    added 258 commits

    • b1f51206...8f32f272 - 245 commits from branch master
    • c58ec9d8 - Add search functionality to incident timeline resolver
    • 8ac73862 - Fix rubocop errors
    • db6b56dc - Refactor timeline tag list
    • 077c3a17 - Add field to resolver [skip ci]
    • 88e2c879 - Fix id type, generate docs [ci skip]
    • 30671ef6 - Fix rubocop errors [ci skip]
    • 56854203 - Use correct type [skip ci]
    • 4a65d377 - Update graphql docs [skip ci]
    • 91e78db4 - Use active record find method
    • ff613088 - Check if params are present
    • 5c2119a9 - Implement search by tag name in timeline finder
    • ef47e536 - Fix rubocop errors [skip ci]
    • 2ac397b6 - Remove local logging

    Compare with previous version

  • changed milestone to %15.10

  • Tristan Read added 2069 commits

    added 2069 commits

    Compare with previous version

  • Tristan Read added 2 commits

    added 2 commits

    Compare with previous version

  • Tristan Read added 2 commits

    added 2 commits

    Compare with previous version

  • Tristan Read added 1 commit

    added 1 commit

    • 2b276c4e - Update scope name and remove comment

    Compare with previous version

  • 29 29 through: :timeline_event_tag_links
    30 30
    31 31 scope :order_occurred_at_asc_id_asc, -> { reorder(occurred_at: :asc, id: :asc) }
    32 scope :with_tag_name, ->(tag_name) do
  • Tristan Read added 1 commit

    added 1 commit

    Compare with previous version

  • Tristan Read added 1 commit

    added 1 commit

    • 029ed46e - [skip ci] add in-progress model specs

    Compare with previous version

  • Ghost User
  • Tristan Read added 315 commits

    added 315 commits

    Compare with previous version

  • Tristan Read added 770 commits

    added 770 commits

    Compare with previous version

  • Tristan Read added 283 commits

    added 283 commits

    Compare with previous version

  • Tristan Read added 2 commits

    added 2 commits

    • 9e930222 - [ci skip] remove extraneous spec setup data
    • a97912e3 - [ci skip] Fix rubocop error

    Compare with previous version

  • Tristan Read added 1 commit

    added 1 commit

    Compare with previous version

  • 14 14 required: true,
    15 15 description: 'ID of the incident.'
    16 16
    17 argument :tag_name,
    18 GraphQL::Types::String,
    19 required: false,
    20 description: 'Name of the tag to search on.',
    21 default_value: nil
    • Comment on lines +17 to +21
      Author Maintainer

      Current behavior:

      • Providing a tag name in the query will filter timeline events to those that are tagged with the name provided.
      • It is not a 'contains' search, it requires an exact match.
        • We could make this a 'contains' search. We could also make the filter case insensitive / trim whitespace if that's beneficial.
      • To filter on multiple tags, the graphql query must be triggered twice. The UI calls for start time and end time events to selected.
        • We could make this a 'multiple' search, e.g. providing an array of terms.
    • Author Maintainer

      My main worry is whether calling the filter twice will cause more load on the database than doing it a different way. I don't have much rails/postgres experience so I can't say whether this is sub-optimal, or whether some level of caching will kick in and make it ok.

    • I think a query plan with some production can hint at potential performance issues.

    • Author Maintainer

      Query plan added here !109293 (comment 1325355202)

    • Given that the query will ftech all rows matching for given issue_id, executing it twice will be using cached buffers, so it should be fine. And we expect not to have many matching rows per issue_id, right?

    • Please register or sign in to reply
  • Tristan Read marked this merge request as ready

    marked this merge request as ready

  • 12 12 return ::IncidentManagement::TimelineEvent.none unless allowed?
    13 13
    14 14 collection = incident.incident_management_timeline_events
    15
    16 collection = collection.with_tag_name(params[:tag_name]) if params[:tag_name]
    • Question Do you mind providing query plans for this new query? :pray:

      See https://docs.gitlab.com/ee/development/database_review.html#queries

    • Author Maintainer

      Thanks for the link, I'll look into this :thumbsup_tone1:

    • Author Maintainer

      @splattael Thanks for the pointer, it's led to some great learning.

      Regarding the query plan - I found the exact query (via logs/database.log) and ran explain on it locally and in database lab.

      Here are the the results:

      Query
      SELECT
          "incident_management_timeline_events".*
      FROM
          "incident_management_timeline_events"
          INNER JOIN "incident_management_timeline_event_tag_links" ON "incident_management_timeline_event_tag_links"."timeline_event_id" = "incident_management_timeline_events"."id"
          INNER JOIN "incident_management_timeline_event_tags" "timeline_event_tags" ON "timeline_event_tags"."id" = "incident_management_timeline_event_tag_links"."timeline_event_tag_id"
      WHERE
          "incident_management_timeline_events"."issue_id" = 451
          AND "timeline_event_tags"."name" = 'Start time'
      ORDER BY
          "incident_management_timeline_events"."occurred_at" ASC,
          "incident_management_timeline_events"."id" ASC
      LIMIT 101
      Explain plan (database lab)
       Limit  (cost=12.82..12.82 rows=1 width=377) (actual time=6.796..6.799 rows=0 loops=1)
         Buffers: shared hit=9 read=3
         I/O Timings: read=6.742 write=0.000
         ->  Sort  (cost=12.82..12.82 rows=1 width=377) (actual time=6.794..6.797 rows=0 loops=1)
               Sort Key: incident_management_timeline_events.occurred_at, incident_management_timeline_events.id
               Sort Method: quicksort  Memory: 25kB
               Buffers: shared hit=9 read=3
               I/O Timings: read=6.742 write=0.000
               ->  Nested Loop  (cost=1.00..12.81 rows=1 width=377) (actual time=6.771..6.773 rows=0 loops=1)
                     Buffers: shared hit=3 read=3
                     I/O Timings: read=6.742 write=0.000
                     ->  Nested Loop  (cost=0.56..9.35 rows=1 width=385) (actual time=6.771..6.772 rows=0 loops=1)
                           Buffers: shared hit=3 read=3
                           I/O Timings: read=6.742 write=0.000
                           ->  Index Scan using index_im_timeline_events_issue_id on public.incident_management_timeline_events  (cost=0.42..3.80 rows=2 width=377) (actual time=6.770..6.771 rows=0 loops=1)
                                 Index Cond: (incident_management_timeline_events.issue_id = 451)
                                 Buffers: shared hit=3 read=3
                                 I/O Timings: read=6.742 write=0.000
                           ->  Index Only Scan using index_im_timeline_event_tags_on_tag_id_and_event_id on public.incident_management_timeline_event_tag_links  (cost=0.14..2.76 rows=1 width=16) (actual time=0.000..0.000 rows=0 loops=0)
                                 Index Cond: (incident_management_timeline_event_tag_links.timeline_event_id = incident_management_timeline_events.id)
                                 Heap Fetches: 0
                                 I/O Timings: read=0.000 write=0.000
                     ->  Index Scan using incident_management_timeline_event_tags_pkey on public.incident_management_timeline_event_tags timeline_event_tags  (cost=0.43..3.46 rows=1 width=8) (actual time=0.000..0.000 rows=0 loops=0)
                           Index Cond: (timeline_event_tags.id = incident_management_timeline_event_tag_links.timeline_event_tag_id)
                           Filter: (timeline_event_tags.name = 'Start time'::text)
                           Rows Removed by Filter: 0
                           I/O Timings: read=0.000 write=0.000
      Explain plan (local gdk)
       Limit  (cost=16.15..16.15 rows=1 width=173) (actual time=0.101..0.103 rows=2 loops=1)
         ->  Sort  (cost=16.15..16.15 rows=1 width=173) (actual time=0.099..0.100 rows=2 loops=1)
               Sort Key: incident_management_timeline_events.occurred_at, incident_management_timeline_events.id
               Sort Method: quicksort  Memory: 25kB
               ->  Nested Loop  (cost=0.45..16.14 rows=1 width=173) (actual time=0.072..0.080 rows=2 loops=1)
                     ->  Nested Loop  (cost=0.30..14.87 rows=7 width=181) (actual time=0.062..0.068 rows=2 loops=1)
                           ->  Index Scan using index_im_timeline_events_issue_id on incident_management_timeline_events  (cost=0.15..3.18 rows=2 width=173) (actual time=0.028..0.036 rows=14 loops=1)
                                 Index Cond: (issue_id = 451)
                           ->  Index Scan using index_im_timeline_event_id on incident_management_timeline_event_tag_links  (cost=0.15..5.78 rows=7 width=16) (actual time=0.002..0.002 rows=0 loops=14)
                                 Index Cond: (timeline_event_id = incident_management_timeline_events.id)
                     ->  Index Scan using incident_management_timeline_event_tags_pkey on incident_management_timeline_event_tags timeline_event_tags  (cost=0.15..0.18 rows=1 width=8) (actual time=0.004..0.004 rows=1 loops=2)
                           Index Cond: (id = incident_management_timeline_event_tag_links.timeline_event_tag_id)
                           Filter: (name = 'Start time'::text)
       Planning Time: 0.459 ms
       Execution Time: 0.150 ms
      (15 rows)

      Link to database lab result: https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/16928/commands/57538

      I'm not sure how valid the database lab results are. It's selecting events on a specific issue that I created locally, so the results will be empty. I see that I can do an update to add data to postgres.ai but I'll need to learn some more before trying this.

      • Does this look good enough to send for database review?
      • Do we have any projects in database lab that already have timeline events?
    • @tristan.read Would we always query with issue_id when querying the tag name? It doesn't seem we need a new index for that as I'd think the number of tags for incident won't be many, but I think we might need to add a new index if we need to query by imeline_event_tags.name alone.

    • Author Maintainer

      @dskim_gitlab The planned UI would always use the issue_id, indeed :thumbsup_tone1:

      I'm not sure what direct users of the API might do, but I doubt that it would be a common use case.

    • Here is an execution plan with actual production data - https://console.postgres.ai/gitlab/gitlab-production-tunnel-pg12/sessions/19201/commands/63391.

      The plan is not ideal, and execution can be slow in some circumstances, but if we can assume that 1) An issue does not have many incident_management_timeline_events, and 2) timeline events does not have many incident_management_timeline_event_tags, which I think are both reasonable assumptions, I think we'll be fine.

      It's interesting that we have many millions of incident_management_timeline_event_tags, but very few records in incident_management_timeline_event_tag_links, maybe because this is a new feature?

    • Author Maintainer

      :thinking: That's interesting @krasio - I'd have guessed there would be a similar number of tags and links, since a link is what attaches a tag to a timeline event. Tags with no links would be 'orphans'. I'll ask one of the other backend devs on my team about this.

    • Please register or sign in to reply
  • mentioned in issue #360009 (closed)

  • Tristan Read added 2167 commits

    added 2167 commits

    Compare with previous version

  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Please register or sign in to reply
    Loading