Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[visualization]Externalize all filterable vega fields #131

Open
YANG-DB opened this issue Feb 20, 2024 · 0 comments
Open

[visualization]Externalize all filterable vega fields #131

YANG-DB opened this issue Feb 20, 2024 · 0 comments
Assignees
Labels
integration integration related content visualization a visual widget for a specific purpose

Comments

@YANG-DB
Copy link
Member

YANG-DB commented Feb 20, 2024

What is the integration resource ?

Externalize all filterable fields in the visualization vega widgets

What is the integration source protocol ?

The purpose of this issue is to align all the existing / new vega visualization with the notion of externalized filters that are exported from vega back to the dashboard using the dashboard build-in vega functions.

Which Dashboards would you be using ?

Here is a sample for this capability :

Screen.Recording.2024-02-20.at.12.06.16.PM.mov

Do you have any additional context?

See attached vega services requests rate spec as an example:

  • externalize @timestamp field as time filter
  • externalize serviceName field as dimensional filter
{
  "$schema": "https://vega.github.io/schema/vega/v3.json",
  "title": "Services Requests Rate",
  "data": [
    {
      "name": "rawdata",
      "url": {
        "%context%": true,
        "%timefield%": "@timestamp",
        "index": "otel-v1-apm-span-fixed",
        "body": {
          "aggs": {
            "services": {
              "terms": {
                "field": "serviceName",
                "size": 10
              },
              "aggs": {
                "time_buckets": {
                  "date_histogram": {
                    "field": "@timestamp",
                    "interval": {"%autointerval%": true},
                    "extended_bounds": {
                      "min": {"%timefilter%": "min"},
                      "max": {"%timefilter%": "max"}
                    },
                    "min_doc_count": 0
                  }
                }
              }
            }
          },
          "size": 0
        }
      },
      "format": {"property": "aggregations.services.buckets"}
    },
    {
      "name": "flatdata",
      "source": "rawdata",
      "transform": [
        {
          "type": "flatten",
          "fields": ["time_buckets.buckets"],
          "as": ["val"]
        },
        {
          "type": "formula",
          "as": "count",
          "expr":"datum.val.doc_count"
        },

        {
          "type": "formula",
          "as": "time",
          "expr":"datum.val.key_as_string"
        }

      ]
    },
    {
      "name": "hasSelection",
      "values": [{}],
      "transform": [
        {"type": "filter", "expr": "selected[0] != selected[1]"}
      ]
    }
  ],
  "scales": [
    {
      "name": "groupScale",
      "type": "band",
      "padding": 0.1,
      "domain": {"data": "rawdata", "field": "key", "sort": true},
      "range": "height"
    },
    {
      "name": "xScale",
      "type": "time",
      "domain": {"data": "flatdata", "field": "val.key"},
      "range": "width",
      "padding": 5
    },
    {
      "name": "yScale",
      "type": "linear",
      "domain": {"data": "flatdata", "field": "val.doc_count"},
      "range": [{"signal": "bandwidth('groupScale')"}, 0]
    },
    {
      "name": "colorScale",
      "type": "ordinal",
      "domain": {"data": "rawdata", "field": "key", "sort": true},
      "range": "category"
    }
  ],
  "axes": [
    {"orient": "bottom", "scale": "xScale", "tickCount": 5}
  ],
  "signals": [
    {
      "name": "currentX",
      "value": -1,
      "on": [
        {"events": "view:mousemove", "update": "clamp(x(), 0, width)"},
        {"events": "view:mouseout", "update": "-1"}
      ]
    },
    {
      "name": "selected",
      "value": [0, 0],
      "on": [
        {"events": "@grapharea:mousedown", "update": "[x(), x()]"},
        {
          "events": "[@grapharea:mousedown, window:mouseup] > window:mousemove!",
          "update": "[selected[0], clamp(x(), 0, width)]"
        },
        {
          "events": {"signal": "delta"},
          "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
        },
        {
          "events": "[@leftEdge:mousedown, window:mouseup] > window:mousemove!",
          "update": "[clamp(x(), 0, width), selected[1]]"
        },
        {
          "events": "[@rightEdge:mousedown, window:mouseup] > window:mousemove!",
          "update": "[selected[0], clamp(x(), 0, width)]"
        }
      ]
    },
    {
      "name": "anchor",
      "value": null,
      "on": [
        {"events": "@selectedRect:mousedown", "update": "selected"}
      ]
    },
    {
      "name": "xDown",
      "value": 0,
      "on": [
        {"events": "@selectedRect:mousedown", "update": "x()"}
      ]
    },
    {
      "name": "delta",
      "value": 0,
      "on": [
        {"events": "[@selectedRect:mousedown, window:mouseup] > window:mousemove!", "update": "x() - xDown"}
      ]
    },
    {
      "name": "applyTimeFilter",
      "on": [
        {
          "events": "@applyTimeFilterButton:click, @selectedRect:dblclick",
          "update": "opensearchDashboardsSetTimeFilter(invert('xScale',selected[0]), invert('xScale',selected[1]))"
        },
        {"events": "@grapharea:dblclick", "update": "opensearchDashboardsSetTimeFilter('now-15d', 'now')"}
      ]
    }
  ],
  "marks": [
    {
      "name": "grapharea",
      "type": "group",
      "from": {
        "facet": {"name": "facets", "data": "rawdata", "field": "time_buckets.buckets"}
      },
      "encode": {
        "update": {
          "y": {"scale": "groupScale", "field": "key"},
          "height": {"scale": "groupScale", "band": 1},
          "x": {"value": 0},
          "width": {"signal": "width"},
          "fillOpacity": {"value": 0},
          "fill": {"value": "#000"},
          "tooltip": {
            "signal": "{'Service': datum['key']}"
          }
        }
      },
      "axes": [
        {
          "orient": "left",
          "scale": "yScale",
          "title": {"signal": "parent.key"},
          "tickCount": 7,
          "encode": {
            "title": {
              "name": "extFilter",
              "interactive": true,
              "update": {
                "cursor": {"value": "pointer"},
                "fontSize": {"value": 8},
                "fill": {"value": "steelblue"}
              },
              "hover": {
                "fill": {"value": "firebrick"}
              }
            }
          }
        }
      ],
      "signals": [
        {
          "name": "updateFilterObj",
          "on": [
            {
              "events": {"source": "scope", "markname": "extFilter", "type": "click"},
              "update": "{'match': {'serviceName': {'query': parent.key, 'type': 'phrase'}}}"
            }
          ]
        },
        {
          "name": "applyFilterObj",
          "on": [
            {
              "events": {"signal": "updateFilterObj"},
              "update": "length(domain('groupScale')) > 1 ? opensearchDashboardsAddFilter(updateFilterObj) : opensearchDashboardsRemoveFilter(updateFilterObj)"
            }
          ]
        }
      ],
      "marks": [
        {
          "type": "line",
          "from": {"data": "facets"},
          "interactive": false,
          "encode": {
            "update": {
              "x": {"scale": "xScale", "field": "key"},
              "y": {"scale": "yScale", "field": "doc_count"},
              "fill": {"scale": "colorScale", "field": {"parent": "key"}},
              "fillOpacity": {"value": 0.2},
              "stroke": {"scale": "colorScale", "field": {"parent": "key"}}
            }
          }
        },
        {
          type: symbol
          style: ["point"]
          from: {
            data: facets
          }
          encode: {
            update: {
              x: {
                scale: xScale
                field: key
              }
              y: {
                scale: yScale
                field: doc_count
              }
              size: {
                value: 8
              }
              "stroke": {"scale": "colorScale", "field": {"parent": "key"}}
              "tooltip": {
                "signal": "{'Date': timeFormat(datum['key'], '%Y-%m-%d %H:%M'), 'Value': datum['doc_count']}"
              }
            }
          }
        }
        {
          "type": "group",
          "from": {"data": "hasSelection"},
          "marks": [
            {
              "name": "selectedRect",
              "type": "rect",
              "encode": {
                "update": {
                  "height": {"scale": "groupScale", "band": 1},
                  "fill": {"value": "#333"},
                  "fillOpacity": {"value": 0.2},
                  "cursor": {"value": "move"},
                  "x": {"signal": "selected[0]"},
                  "x2": {"signal": "selected[1]"}
                }
              }
            },
            {
              "name": "leftEdge",
              "type": "rect",
              "encode": {
                "update": {
                  "height": {"scale": "groupScale", "band": 1},
                  "width": {"value": 2},
                  "fill": {"value": "firebrick"},
                  "cursor": {"value": "ew-resize"},
                  "x": {"signal": "selected[0]"}
                }
              }
            },
            {
              "name": "rightEdge",
              "type": "rect",
              "encode": {
                "update": {
                  "height": {"scale": "groupScale", "band": 1},
                  "width": {"value": 2},
                  "fill": {"value": "firebrick"},
                  "cursor": {"value": "ew-resize"},
                  "x": {"signal": "selected[1]"}
                }
              }
            }
          ]
        }
      ]
    },
    {
      "type": "group",
      "name": "applyTimeFilterButton",
      "from": {"data": "hasSelection"},
      "encode": {
        "update": {
          "cursor": {"value": "pointer"},
          "cornerRadius": {"value": 6},
          "fill": {"value": "#f5f5f5"},
          "stroke": {"value": "#c1c1c1"},
          "strokeWidth": {"value": 2},
          "xc": {"signal": "width/2"},
          "y": {"value": 30},
          "width": {"value": 80},
          "height": {"value": 30},
          "opacity": {"value": 1}
        },
        "hover": {
          "opacity": {"value": 0.7}
        }
      },
      "marks": [
        {
          "type": "text",
          "interactive": false,
          "encode": {
            "update": {
              "xc": {"field": {"group": "width"}, "mult": 0.5},
              "yc": {"field": {"group": "height"}, "mult": 0.5, "offset": 2},
              "align": {"value": "center"},
              "baseline": {"value": "middle"},
              "fontWeight": {"value": "bold"},
              "text": {"value": "Apply Filter"}
            }
          }
        }
      ]
    },
    {
      "type": "rule",
      "interactive": false,
      "encode": {
        "update": {
          "y": {"value": 0},
          "y2": {"signal": "height"},
          "stroke": {"value": "gray"},
          "strokeDash": {"value": [2, 1]},
          "x": {"signal": "max(currentX,0)"},
          "strokeOpacity": {"signal": "currentX > 0 ? 1 : 0"}
        }
      }
    }
  ]
}

@YANG-DB YANG-DB added untriaged integration integration related content visualization a visual widget for a specific purpose labels Feb 20, 2024
@YANG-DB YANG-DB self-assigned this Feb 20, 2024
@YANG-DB YANG-DB removed the untriaged label Mar 27, 2024
@YANG-DB YANG-DB added this to catalog Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration integration related content visualization a visual widget for a specific purpose
Projects
Status: No status
Development

No branches or pull requests

1 participant