Merge branch 'main' into ifrost/graphite-on-react-4
This commit is contained in:
+34
-1
@@ -235,6 +235,9 @@ services:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_USER: grafana
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- pull_request
|
||||
@@ -563,6 +566,9 @@ services:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_USER: grafana
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
@@ -663,6 +669,9 @@ steps:
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
@@ -1005,6 +1014,9 @@ services:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_USER: grafana
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/tags/v*
|
||||
@@ -1477,6 +1489,9 @@ services:
|
||||
image_pull_secrets:
|
||||
- dockerconfigjson
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/tags/v*
|
||||
@@ -1617,6 +1632,9 @@ steps:
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/tags/v*
|
||||
@@ -1948,6 +1966,9 @@ services:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_USER: grafana
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- custom
|
||||
@@ -2414,6 +2435,9 @@ services:
|
||||
image_pull_secrets:
|
||||
- dockerconfigjson
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- custom
|
||||
@@ -2554,6 +2578,9 @@ steps:
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- custom
|
||||
@@ -2860,6 +2887,9 @@ services:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_USER: grafana
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/v*
|
||||
@@ -3325,6 +3355,9 @@ services:
|
||||
image_pull_secrets:
|
||||
- dockerconfigjson
|
||||
|
||||
node:
|
||||
type: no-parallel
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/v*
|
||||
@@ -3496,6 +3529,6 @@ get:
|
||||
|
||||
---
|
||||
kind: signature
|
||||
hmac: 68edb93a18f2e16f8a9b2bf3d21073e181e56d2420feec37ae80b121fc2faeeb
|
||||
hmac: 4f23649a1678c66fb96af929675bcf569cca3b208d425eace86150d981ee9fbb
|
||||
|
||||
...
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
|
||||
<!-- 8.1.1 START -->
|
||||
|
||||
# 8.1.1 (2021-08-09)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* **CloudWatch Logs:** Fix crash when no region is selected. [#37639](https://github.com/grafana/grafana/pull/37639), [@aocenas](https://github.com/aocenas)
|
||||
* **Reporting:** Fix timezone parsing for scheduler (enterprise)
|
||||
|
||||
<!-- 8.1.1 END -->
|
||||
<!-- 8.1.0 START -->
|
||||
|
||||
# 8.1.0 (2021-08-05)
|
||||
|
||||
+107
-19
@@ -22,7 +22,7 @@ Family: scuemata.#Family & {
|
||||
// Theme of dashboard.
|
||||
style: *"light" | "dark"
|
||||
// Timezone of dashboard,
|
||||
timezone?: *"browser" | "utc"
|
||||
timezone?: *"browser" | "utc" | ""
|
||||
// Whether a dashboard is editable or not.
|
||||
editable: bool | *true
|
||||
// 0 for no shared crosshair or tooltip (default).
|
||||
@@ -66,14 +66,62 @@ Family: scuemata.#Family & {
|
||||
showIn: number | *0
|
||||
}]
|
||||
// Auto-refresh interval.
|
||||
refresh?: string
|
||||
refresh?: string | false
|
||||
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||
// changes to said schema.
|
||||
schemaVersion: number | *25
|
||||
// FIXME this is the old schema numbering system, and will be replaced by scuemata
|
||||
schemaVersion: number | *30
|
||||
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||
version?: number
|
||||
panels?: [...#Panel]
|
||||
|
||||
// TODO docs
|
||||
#FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(targetType="enum")
|
||||
|
||||
// TODO docs
|
||||
#FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(targetType="type")
|
||||
|
||||
// TODO docs
|
||||
#FieldColor: {
|
||||
// The main color scheme mode
|
||||
mode: #FieldColorModeId | string
|
||||
// Stores the fixed color value if mode is fixed
|
||||
fixedColor?: string
|
||||
// Some visualizations need to know how to assign a series color from by value color schemes
|
||||
seriesBy?: #FieldColorSeriesByMode
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
// TODO docs
|
||||
#Threshold: {
|
||||
// TODO docs
|
||||
// FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON
|
||||
value?: number
|
||||
// TODO docs
|
||||
color: string
|
||||
// TODO docs
|
||||
// TODO are the values here enumerable into a disjunction?
|
||||
// Some seem to be listed in typescript comment
|
||||
state?: string
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
#ThresholdsMode: "absolute" | "percentage" @cuetsy(targetType="enum")
|
||||
|
||||
#ThresholdsConfig: {
|
||||
mode: #ThresholdsMode
|
||||
|
||||
// Must be sorted by 'value', first value is always -Infinity
|
||||
steps: [...#Threshold]
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
// Schema for panel targets is specified by datasource
|
||||
// plugins. We use a placeholder definition, which the Go
|
||||
// schema loader either left open/as-is with the Base
|
||||
// variant of the Dashboard and Panel families, or filled
|
||||
// with types derived from plugins in the Instance variant.
|
||||
// When working directly from CUE, importers can extend this
|
||||
// type directly to achieve the same effect.
|
||||
#Target: {...}
|
||||
|
||||
// Dashboard panels. Panels are canonically defined inline
|
||||
// because they share a version timeline with the dashboard
|
||||
// schema; they do not vary independently. We create a separate,
|
||||
@@ -83,6 +131,15 @@ Family: scuemata.#Family & {
|
||||
// The panel plugin type id.
|
||||
type: !=""
|
||||
|
||||
// TODO docs
|
||||
id?: number
|
||||
|
||||
// FIXME this almost certainly has to be changed in favor of scuemata versions
|
||||
pluginVersion?: string
|
||||
|
||||
// TODO docs
|
||||
tags?: [...string]
|
||||
|
||||
// Internal - the exact major and minor versions of the panel plugin
|
||||
// schema. Hidden and therefore not a part of the data model, but
|
||||
// expected to be filled with panel plugin schema versions so that it's
|
||||
@@ -93,6 +150,9 @@ Family: scuemata.#Family & {
|
||||
// TODO 2-tuple list instead of struct?
|
||||
panelSchema?: { maj: number, min: number }
|
||||
|
||||
// TODO docs
|
||||
targets?: [...#Target]
|
||||
|
||||
// Panel title.
|
||||
title?: string
|
||||
// Description.
|
||||
@@ -115,20 +175,33 @@ Family: scuemata.#Family & {
|
||||
static?: bool
|
||||
}
|
||||
// Panel links.
|
||||
// links?: [..._panelLink]
|
||||
// FIXME this is temporarily specified as a closed list so
|
||||
// that validation will pass when no links are present, but
|
||||
// to force a failure as soon as it's checked against there
|
||||
// being anything in the list so it can be fixed in
|
||||
// accordance with that object
|
||||
links?: []
|
||||
|
||||
// Name of template variable to repeat for.
|
||||
repeat?: string
|
||||
// Direction to repeat in if 'repeat' is set.
|
||||
// "h" for horizontal, "v" for vertical.
|
||||
repeatDirection: *"h" | "v"
|
||||
// Schema for panel targets is specified by datasource
|
||||
// plugins. We use a placeholder definition, which the Go
|
||||
// schema loader either left open/as-is with the Base
|
||||
// variant of the Dashboard and Panel families, or filled
|
||||
// with types derived from plugins in the Instance variant.
|
||||
// When working directly from CUE, importers can extend this
|
||||
// type directly to achieve the same effect.
|
||||
targets?: [...{...}]
|
||||
|
||||
// TODO docs
|
||||
maxDataPoints?: number
|
||||
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
interval?: string
|
||||
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
timeFrom?: string
|
||||
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
timeShift?: string
|
||||
|
||||
// The values depend on panel type
|
||||
options: {...}
|
||||
@@ -166,17 +239,25 @@ Family: scuemata.#Family & {
|
||||
min?: number
|
||||
max?: number
|
||||
|
||||
// // Convert input values into a display string
|
||||
// mappings?: ValueMapping[];
|
||||
// Convert input values into a display string
|
||||
//
|
||||
// TODO this one corresponds to a complex type with
|
||||
// generics on the typescript side. Ouch. Will
|
||||
// either need special care, or we'll just need to
|
||||
// accept a very loosely specified schema. It's very
|
||||
// unlikely we'll be able to translate cue to
|
||||
// typescript generics in the general case, though
|
||||
// this particular one *may* be able to work.
|
||||
mappings?: [...{...}]
|
||||
|
||||
// // Map numeric values to states
|
||||
// thresholds?: ThresholdsConfig;
|
||||
// Map numeric values to states
|
||||
thresholds?: #ThresholdsConfig
|
||||
|
||||
// // Map values to a display color
|
||||
// color?: FieldColor;
|
||||
color?: #FieldColor
|
||||
|
||||
// // Used when reducing field values
|
||||
// nullValueMode?: NullValueMode;
|
||||
// nullValueMode?: NullValueMode
|
||||
|
||||
// // The behavior when clicking on a result
|
||||
links?: [...]
|
||||
@@ -184,10 +265,17 @@ Family: scuemata.#Family & {
|
||||
// Alternative to empty string
|
||||
noValue?: string
|
||||
|
||||
// TODO conundrum: marking this struct as open would
|
||||
// - i think - preclude closed validation of
|
||||
// plugin-defined config bits. But, marking it
|
||||
// closed makes it impossible to use just this
|
||||
// schema (the "base" variant) to validate the base
|
||||
// components of a dashboard.
|
||||
//
|
||||
// Can always exist. Valid fields within this are
|
||||
// defined by the panel plugin - that's the
|
||||
// PanelFieldConfig that comes from the plugin.
|
||||
custom?: {}
|
||||
custom?: {...}
|
||||
}
|
||||
overrides: [...{
|
||||
matcher: {
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ ScaleDistribution: "linear" | "log" @c
|
||||
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(targetType="enum")
|
||||
LineStyle: {
|
||||
fill?: "solid" | "dash" | "dot" | "square"
|
||||
dash?: [number]
|
||||
dash?: [...number]
|
||||
} @cuetsy(targetType="interface")
|
||||
LineConfig: {
|
||||
lineColor?: string
|
||||
@@ -68,7 +68,7 @@ AxisConfig: {
|
||||
HideSeriesConfig: {
|
||||
tooltip: bool
|
||||
legend: bool
|
||||
graph: bool
|
||||
viz: bool
|
||||
} @cuetsy(targetType="interface")
|
||||
LegendPlacement: "bottom" | "right" @cuetsy(targetType="type")
|
||||
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(targetType="enum")
|
||||
@@ -86,7 +86,7 @@ GraphFieldConfig: LineConfig & FillConfig & PointsConfig & AxisConfig & {
|
||||
VizLegendOptions: {
|
||||
displayMode: LegendDisplayMode
|
||||
placement: LegendPlacement
|
||||
calcs: [string]
|
||||
calcs: [...string]
|
||||
} @cuetsy(targetType="interface")
|
||||
VizTooltipOptions: {
|
||||
mode: TooltipDisplayMode
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 3151,
|
||||
"links": [],
|
||||
@@ -90,9 +89,7 @@
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "CPU per host",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
@@ -101,33 +98,24 @@
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
"align": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -199,9 +187,7 @@
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Login Count per host",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
@@ -210,33 +196,24 @@
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
"align": false
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -255,4 +232,4 @@
|
||||
"title": "Datasource tests - OpenTSDB v2.3",
|
||||
"uid": "rZRUGik7k",
|
||||
"version": 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 625,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 24,
|
||||
@@ -38,13 +36,10 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"transparent": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
@@ -107,8 +102,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -166,7 +160,6 @@
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 15,
|
||||
@@ -207,8 +200,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
@@ -293,8 +285,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
@@ -343,7 +334,6 @@
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
@@ -386,8 +376,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "#EAB839",
|
||||
@@ -473,8 +462,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "#EAB839",
|
||||
@@ -522,7 +510,6 @@
|
||||
"type": "status-history"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
@@ -573,8 +560,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -713,8 +699,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -757,7 +742,6 @@
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
@@ -783,7 +767,6 @@
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
@@ -848,8 +831,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -924,8 +906,6 @@
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Interpolation mode: smooth",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -969,8 +949,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1045,8 +1024,6 @@
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Interpolation mode: Step before",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -1090,8 +1067,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1166,13 +1142,10 @@
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Interpolation mode: Step after",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
@@ -1240,8 +1213,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1300,8 +1272,6 @@
|
||||
"stringInput": "10,11,12,11,10,11,12,12,11,10,9,10,11,12,10,10,11,12,13,11,10,9,10,11,12,13,14,10,10"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Auto min max",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -1351,8 +1321,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1411,8 +1380,6 @@
|
||||
"stringInput": "10,11,12,11,10,11,12,12,11,10,9,10,11,12,200,10,11,12,13,11,10,9,10,11,12,13,14,10,10"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Hard min 0, max 30",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -1462,8 +1429,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1522,13 +1488,10 @@
|
||||
"stringInput": "10,11,12,11,10,11,12,12,11,10,9,10,11,12,200,10,11,12,13,11,10,9,10,11,12,13,14,10,10"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Soft min 0, soft max 30",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
@@ -1595,8 +1558,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1769,7 +1731,6 @@
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
@@ -1844,8 +1805,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2170,8 +2130,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2493,8 +2452,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2573,7 +2531,6 @@
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "gdev-testdata",
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
@@ -2620,8 +2577,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2655,7 +2611,6 @@
|
||||
"y": 83
|
||||
},
|
||||
"id": 49,
|
||||
"maxDataPoints": null,
|
||||
"options": {
|
||||
"bucketOffset": 0,
|
||||
"bucketSize": 10,
|
||||
@@ -2719,8 +2674,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2754,7 +2708,6 @@
|
||||
"y": 83
|
||||
},
|
||||
"id": 77,
|
||||
"maxDataPoints": null,
|
||||
"options": {
|
||||
"showHeader": true
|
||||
},
|
||||
@@ -2838,4 +2791,4 @@
|
||||
"title": "New Features in v8.0",
|
||||
"uid": "8mux8PqGz",
|
||||
"version": 17
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 26,
|
||||
"w": 6,
|
||||
@@ -34,7 +32,6 @@
|
||||
"id": 7,
|
||||
"links": [],
|
||||
"options": {
|
||||
"folderId": null,
|
||||
"maxItems": 100,
|
||||
"query": "",
|
||||
"showHeadings": true,
|
||||
@@ -45,13 +42,10 @@
|
||||
},
|
||||
"pluginVersion": "8.1.0-pre",
|
||||
"tags": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Starred",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
@@ -75,13 +69,10 @@
|
||||
"tags": [
|
||||
"panel-tests"
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "tag: panel-tests",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
@@ -91,7 +82,6 @@
|
||||
"id": 3,
|
||||
"links": [],
|
||||
"options": {
|
||||
"folderId": null,
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -108,13 +98,10 @@
|
||||
"gdev",
|
||||
"demo"
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "tag: dashboard-demo",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 26,
|
||||
"w": 6,
|
||||
@@ -124,7 +111,6 @@
|
||||
"id": 5,
|
||||
"links": [],
|
||||
"options": {
|
||||
"folderId": null,
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -141,13 +127,10 @@
|
||||
"gdev",
|
||||
"datasource-test"
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Data source tests",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
@@ -173,13 +156,10 @@
|
||||
"templating",
|
||||
"gdev"
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "tag: templating ",
|
||||
"type": "dashlist"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 6,
|
||||
@@ -205,8 +185,6 @@
|
||||
"gdev",
|
||||
"demo"
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "tag: transforms",
|
||||
"type": "dashlist"
|
||||
}
|
||||
@@ -250,4 +228,4 @@
|
||||
"title": "Grafana Dev Overview & Home",
|
||||
"uid": "j6T00KRZz",
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -49,8 +47,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -96,7 +93,6 @@
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "Should be smaller given the longer value",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -123,8 +119,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -170,7 +165,6 @@
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -195,8 +189,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -242,7 +235,6 @@
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -267,8 +259,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -339,8 +330,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -386,7 +376,6 @@
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -412,8 +401,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -459,7 +447,6 @@
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -485,8 +472,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -552,4 +538,4 @@
|
||||
"title": "BarChart - Panel Tests - Value sizing",
|
||||
"uid": "WFlOM-jM1",
|
||||
"version": 9
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
@@ -59,8 +58,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
@@ -104,7 +102,6 @@
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -145,8 +142,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
@@ -192,7 +188,6 @@
|
||||
"startValue": 1
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"title": "Color bars by discrete thresholds",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -238,8 +233,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
@@ -328,8 +322,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
@@ -418,8 +411,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
@@ -467,7 +459,6 @@
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -508,8 +499,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
"color": "blue"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
@@ -590,4 +580,4 @@
|
||||
"title": "Panel Tests - Graph NG - By value color schemes",
|
||||
"uid": "aBXrJ0R7z",
|
||||
"version": 20
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [
|
||||
{
|
||||
@@ -69,8 +68,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -132,8 +130,6 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Show gaps",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -180,8 +176,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -251,8 +246,6 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Gaps & null between every point for series B",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -299,8 +292,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -390,8 +382,6 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "No nulls but unaligned series",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -436,8 +426,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -499,8 +488,6 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Connected",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -547,8 +534,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -618,8 +604,6 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Same as above but connected",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -666,8 +650,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -757,8 +740,6 @@
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Same as above but connected",
|
||||
"type": "timeseries"
|
||||
},
|
||||
@@ -803,8 +784,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -903,8 +883,6 @@
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Null values in first series & show gaps ",
|
||||
"transformations": [],
|
||||
"type": "timeseries"
|
||||
@@ -950,8 +928,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1050,14 +1027,11 @@
|
||||
"stringInput": "10,25,null,null,50,10"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Null values in second series show gaps (bugged)",
|
||||
"transformations": [],
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -1097,8 +1071,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1157,13 +1130,10 @@
|
||||
"stringInput": "1,20,90,null,30,5,0"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Span nulls below 1hr",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -1205,8 +1175,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1264,4 +1233,4 @@
|
||||
"title": "Panel Tests - Graph NG - Gaps and Connected",
|
||||
"uid": "8mmCAF1Mz",
|
||||
"version": 12
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 632,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -40,8 +38,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -100,8 +97,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -139,7 +135,6 @@
|
||||
"type": "histogram"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -160,8 +155,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -212,7 +206,6 @@
|
||||
"type": "histogram"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -233,8 +226,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -285,7 +277,6 @@
|
||||
"type": "histogram"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -300,8 +291,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -353,7 +343,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -368,8 +357,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -439,4 +427,4 @@
|
||||
"title": "Panel Tests - Histogram",
|
||||
"uid": "UTv--wqMk",
|
||||
"version": 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -55,8 +53,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -113,7 +110,6 @@
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -151,8 +147,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -207,7 +202,6 @@
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "Should show gaps",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -246,8 +240,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -301,7 +294,6 @@
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -319,8 +311,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -339,7 +330,6 @@
|
||||
"y": 19
|
||||
},
|
||||
"id": 4,
|
||||
"interval": null,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"alignValue": "center",
|
||||
@@ -411,4 +401,4 @@
|
||||
"title": "Timeline Demo",
|
||||
"uid": "mIJjFy8Kz",
|
||||
"version": 13
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 329,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -34,8 +32,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -165,7 +162,6 @@
|
||||
1616557148000
|
||||
],
|
||||
[
|
||||
null,
|
||||
1616558756000
|
||||
],
|
||||
[
|
||||
@@ -173,7 +169,6 @@
|
||||
1616561658000
|
||||
],
|
||||
[
|
||||
null,
|
||||
1616562446000
|
||||
],
|
||||
[
|
||||
@@ -181,7 +176,6 @@
|
||||
1616564104000
|
||||
],
|
||||
[
|
||||
null,
|
||||
1616564548000
|
||||
],
|
||||
[
|
||||
@@ -197,7 +191,6 @@
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -212,8 +205,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -267,7 +259,6 @@
|
||||
"type": "state-timeline"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -282,8 +273,7 @@
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "#EAB839",
|
||||
@@ -306,7 +296,6 @@
|
||||
"y": 21
|
||||
},
|
||||
"id": 4,
|
||||
"interval": null,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"colWidth": 0.9,
|
||||
|
||||
@@ -20,12 +20,10 @@
|
||||
},
|
||||
"description": "",
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -64,8 +62,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -122,7 +119,6 @@
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -137,8 +133,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -209,7 +204,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -224,8 +218,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -291,7 +284,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -306,8 +298,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -377,7 +368,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -392,8 +382,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -446,7 +435,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -470,8 +458,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -565,4 +552,4 @@
|
||||
"title": "Transforms - Config from query",
|
||||
"uid": "Juj4_7ink",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
@@ -39,8 +38,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -87,8 +85,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -146,7 +143,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -157,8 +153,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -209,7 +204,6 @@
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -221,8 +215,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -286,8 +279,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -334,8 +326,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -393,7 +384,6 @@
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -404,8 +394,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -454,7 +443,6 @@
|
||||
"type": "bargauge"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -466,8 +454,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -530,7 +517,6 @@
|
||||
"type": "gauge"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -541,8 +527,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -612,4 +597,4 @@
|
||||
"title": "Transforms - Rows to fields",
|
||||
"uid": "PMtIInink",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ weight = 400
|
||||
|
||||
An exemplar is a specific trace representative of a repeated pattern of data in a given time interval. It helps you identify higher cardinality metadata from specific events within time series data.
|
||||
|
||||
Suppose your company website is experiencing a surge in traffic volumes. While more than eighty percent of the users are able to access the website in under two seconds, some users are experiencing a higher than normal response time resulting in bad user experience
|
||||
Suppose your company website is experiencing a surge in traffic volumes. While more than eighty percent of the users are able to access the website in under two seconds, some users are experiencing a higher than normal response time resulting in bad user experience.
|
||||
|
||||
To identify the factors that are contributing to the latency, you must compare a trace for a fast response against a trace for a slow response. Given the vast amount of data in a typical production environment, it will be extremely laborious and time-consuming effort.
|
||||
|
||||
|
||||
@@ -3,16 +3,14 @@ title = "Alertmanager"
|
||||
description = "Guide for using Alertmanager in Grafana"
|
||||
keywords = ["grafana", "prometheus", "guide"]
|
||||
aliases = ["/docs/grafana/latest/features/datasources/alertmanager"]
|
||||
weight = 1300
|
||||
weight = 150
|
||||
+++
|
||||
|
||||
# Alertmanager data source
|
||||
|
||||
Grafana includes built-in support for Prometheus Alertmanager. It is presently in alpha and not accessible unless [alpha plugins are enabled in Grafana settings](https://grafana.com/docs/grafana/latest/administration/configuration/#enable_alpha). Once you add it as a data source, you can use the [Grafana alerting UI](https://grafana.com/docs/grafana/latest/alerting/) to manage silences, contact points as well as notification policies. A drop down option in these pages allows you to switch between Grafana and any configured Alertmanager data sources .
|
||||
|
||||
> **Note:** New in Grafana 8.0.
|
||||
|
||||
> **Note:** Currently only the [Cortex implementation of Prometheus alertmanager](https://cortexmetrics.io/docs/proposals/scalable-alertmanager/) is supported.
|
||||
> **Note:** Currently, the [Cortex implementation of Prometheus Alertmanager](https://cortexmetrics.io/docs/proposals/scalable-alertmanager/) is required to edit rules.
|
||||
|
||||
## Provision the Alertmanager data source
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ There are three different authentication methods available. `AWS SDK Default` pe
|
||||
|
||||
### IAM roles
|
||||
|
||||
Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. Providing you have chosen the _AWS SDK Default_ authentication method, and your Grafana server is running on AWS, you can use IAM Roles to handle authentication automically.
|
||||
Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. Providing you have chosen the _AWS SDK Default_ authentication method, and your Grafana server is running on AWS, you can use IAM Roles to handle authentication automatically.
|
||||
|
||||
See the AWS documentation on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title = "Elasticsearch"
|
||||
description = "Guide for using Elasticsearch in Grafana"
|
||||
keywords = ["grafana", "elasticsearch", "guide"]
|
||||
aliases = ["/docs/grafana/latest/features/datasources/elasticsearch"]
|
||||
weight = 400
|
||||
weight = 325
|
||||
+++
|
||||
|
||||
# Using Elasticsearch in Grafana
|
||||
|
||||
@@ -3,7 +3,7 @@ title = "Google Cloud Monitoring"
|
||||
description = "Guide for using Google Cloud Monitoring in Grafana"
|
||||
keywords = ["grafana", "stackdriver", "google", "guide", "cloud", "monitoring"]
|
||||
aliases = ["/docs/grafana/latest/features/datasources/stackdriver", "/docs/grafana/next/datasources/cloudmonitoring/", "/docs/grafana/next/features/datasources/cloudmonitoring/"]
|
||||
weight = 200
|
||||
weight = 350
|
||||
+++
|
||||
|
||||
# Using Google Cloud Monitoring in Grafana
|
||||
|
||||
@@ -21,10 +21,11 @@ datasources:
|
||||
access: proxy
|
||||
database: site
|
||||
user: grafana
|
||||
password: grafana
|
||||
url: http://localhost:8086
|
||||
jsonData:
|
||||
httpMode: GET
|
||||
secureJsonData:
|
||||
password: grafana
|
||||
```
|
||||
|
||||
## InfluxDB 2.x for Flux example
|
||||
|
||||
@@ -285,9 +285,9 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
|
||||
|
||||
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../auth/generic-oauth.md" >}}), your data source plugin can reuse the access token for the logged-in Grafana user.
|
||||
|
||||
To allow Grafana to pass the access token to the plugin, the user needs to enable **Forward OAuth Identity** on the data source configuration page. This tells Grafana to pass the token to the plugin in a Authorization header. Note that your plugin needs to use the [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) component in the configuration editor to expose the setting for your data source plugin.
|
||||
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the` jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) provides a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
|
||||
|
||||
The Authorization header is available on the `DataQuery` object on the query data request in your backend data source.
|
||||
When configured, Grafana will pass the user's token to the plugin in an Authorization header, available on the `QueryDataRequest` object on the `QueryData` request in your backend data source.
|
||||
|
||||
```go
|
||||
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
|
||||
@@ -39,7 +39,7 @@ The following list contains fine-grained access control actions.
|
||||
| `reports:send` | `reports:*` | Send a report email. |
|
||||
| `reports.settings:write` | n/a | Update report settings. |
|
||||
| `reports.settings:read` | n/a | Read report settings. |
|
||||
| `provisioning:reload` | `service:accesscontrol` | Reload provisioning files. |
|
||||
| `provisioning:reload` | `services:accesscontrol` | Reload provisioning files. |
|
||||
| `users:read` | `global:users:*` | Read or search user profiles. |
|
||||
| `users:write` | `global:users:*` | Update a user’s profile. |
|
||||
| `users.teams:read` | `global:users:*` | Read a user’s teams. |
|
||||
@@ -62,7 +62,7 @@ The following list contains fine-grained access control actions.
|
||||
| `ldap.user:sync` | n/a | Sync a user via LDAP. |
|
||||
| `ldap.status:read` | n/a | Verify the LDAP servers’ availability. |
|
||||
| `ldap.config:reload` | n/a | Reload the LDAP configuration. |
|
||||
| `status:accesscontrol` | `service:accesscontrol` | Get access-control enabled status. |
|
||||
| `status:accesscontrol` | `services:accesscontrol` | Get access-control enabled status. |
|
||||
| `settings:read` | `settings:**`<br>`settings:auth.saml:*`<br>`settings:auth.saml:enabled` (property level) | Read settings |
|
||||
| `settings:write` | `settings:**`<br>`settings:auth.saml:*`<br>`settings:auth.saml:enabled` (property level) | Update settings |
|
||||
| `server.stats:read` | n/a | Read server stats |
|
||||
@@ -72,12 +72,12 @@ The following list contains fine-grained access control actions.
|
||||
|
||||
The following list contains fine-grained access control scopes.
|
||||
|
||||
| Scopes | Descriptions |
|
||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `roles:*` | Restrict an action to a set of roles. For example, `roles:*` matches any role, `roles:randomuid` matches only the role with UID `randomuid` and `roles:custom:reports:{editor,viewer}` matches both `custom:reports:editor` and `custom:reports:viewer` roles. |
|
||||
| `permissions:delegate` | The scope is only applicable for roles associated with the Access Control itself and indicates that you can delegate your permissions only, or a subset of it, by creating a new role or making an assignment. |
|
||||
| `reports:*` | Restrict an action to a set of reports. For example, `reports:*` matches any report and `reports:1` matches the report with id `1`. |
|
||||
| `service:accesscontrol` | Restrict an action to target only the fine-grained access control service. For example, you can use this in conjunction with the `provisioning:reload` or the `status:accesscontrol` actions. |
|
||||
| `global:users:*` | Restrict an action to a set of global users. |
|
||||
| `users:*` | Restrict an action to a set of users from an organization. |
|
||||
| `settings:**` | Restrict an action to a subset of settings. For example, `settings:**` matches all settings, `settings:auth.saml:*` matches all SAML settings, and `settings:auth.saml:enabled` matches the enable property on the SAML settings. |
|
||||
| Scopes | Descriptions |
|
||||
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `roles:*` | Restrict an action to a set of roles. For example, `roles:*` matches any role, `roles:randomuid` matches only the role with UID `randomuid` and `roles:custom:reports:{editor,viewer}` matches both `custom:reports:editor` and `custom:reports:viewer` roles. |
|
||||
| `permissions:delegate` | The scope is only applicable for roles associated with the Access Control itself and indicates that you can delegate your permissions only, or a subset of it, by creating a new role or making an assignment. |
|
||||
| `reports:*` | Restrict an action to a set of reports. For example, `reports:*` matches any report and `reports:1` matches the report with id `1`. |
|
||||
| `services:accesscontrol` | Restrict an action to target only the fine-grained access control service. For example, you can use this in conjunction with the `provisioning:reload` or the `status:accesscontrol` actions. |
|
||||
| `global:users:*` | Restrict an action to a set of global users. |
|
||||
| `users:*` | Restrict an action to a set of users from an organization. |
|
||||
| `settings:**` | Restrict an action to a subset of settings. For example, `settings:**` matches all settings, `settings:auth.saml:*` matches all SAML settings, and `settings:auth.saml:enabled` matches the enable property on the SAML settings. |
|
||||
|
||||
@@ -26,10 +26,11 @@ Returns an indicator to check if fine-grained access control is enabled or not.
|
||||
| -------------------- | ---------------------- |
|
||||
| status:accesscontrol | services:accesscontrol |
|
||||
|
||||
|
||||
#### Example request
|
||||
|
||||
```http
|
||||
GET /api/access-control/check
|
||||
GET /api/access-control/status
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
```
|
||||
@@ -61,6 +62,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
`GET /api/access-control/roles`
|
||||
|
||||
Gets all existing roles. The response contains all global and organization local roles, for the organization which user is signed in.
|
||||
|
||||
Refer to the [Role scopes]({{< relref "../enterprise/access-control/roles.md#built-in-role-assignments" >}}) for more information.
|
||||
|
||||
#### Required permissions
|
||||
@@ -254,6 +256,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
|
||||
#### Status codes
|
||||
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ---------------------------------------------------------------------------------- |
|
||||
| 200 | Role is updated. |
|
||||
@@ -276,6 +279,7 @@ For example, if a user does not have required permissions for creating users, th
|
||||
| ----------- | -------------------- |
|
||||
| roles:write | permissions:delegate |
|
||||
|
||||
|
||||
#### Example request
|
||||
|
||||
```http
|
||||
@@ -284,16 +288,17 @@ Accept: application/json
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"version": 2,
|
||||
"name": "custom:delete:create:roles",
|
||||
"description": "My custom role which gives users permissions to delete and create roles",
|
||||
"version": 3,
|
||||
"name": "custom:delete:write:roles",
|
||||
"description": "My custom role which gives users permissions to delete and write roles",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
{
|
||||
"action": "roles:delete",
|
||||
"scope": "permissions:delegate"
|
||||
},
|
||||
{
|
||||
"action": "roles:create",
|
||||
{
|
||||
"action": "roles:write",
|
||||
"scope": "permissions:delegate"
|
||||
}
|
||||
]
|
||||
@@ -323,25 +328,27 @@ HTTP/1.1 200 OK
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
{
|
||||
"version": 3,
|
||||
"name": "custom:delete:create:roles",
|
||||
"description": "My custom role which gives users permissions to delete and create roles",
|
||||
"permissions": [
|
||||
"version":3,
|
||||
"uid":"jZrmlLCGka",
|
||||
"name":"custom:delete:write:roles",
|
||||
"description":"My custom role which gives users permissions to delete and write roles",
|
||||
"permissions":[
|
||||
{
|
||||
"action": "roles:delete",
|
||||
"scope": "permissions:delegate",
|
||||
"updated": "2021-05-13T23:19:46.546146+02:00",
|
||||
"created": "2021-05-13T23:19:46.546146+02:00"
|
||||
"action":"roles:delete",
|
||||
"scope":"permissions:delegate",
|
||||
"updated":"2021-08-06T18:27:40+02:00",
|
||||
"created":"2021-08-06T18:27:40+02:00"
|
||||
},
|
||||
{
|
||||
"action": "roles:create",
|
||||
"scope": "permissions:delegate",
|
||||
"updated": "2021-05-13T23:19:46.546146+02:00",
|
||||
"created": "2021-05-13T23:19:46.546146+02:00"
|
||||
{
|
||||
"action":"roles:write",
|
||||
"scope":"permissions:delegate",
|
||||
"updated":"2021-08-06T18:27:41+02:00",
|
||||
"created":"2021-08-06T18:27:41+02:00"
|
||||
}
|
||||
],
|
||||
"updated": "2021-05-13T23:19:46.540987+02:00",
|
||||
"created": "2021-05-13T23:19:46.540986+02:00"
|
||||
"updated":"2021-08-06T18:27:41+02:00",
|
||||
"created":"2021-08-06T18:27:40+02:00",
|
||||
"global":true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -370,10 +377,11 @@ For example, if a user does not have required permissions for creating users, th
|
||||
| ------------ | -------------------- |
|
||||
| roles:delete | permissions:delegate |
|
||||
|
||||
|
||||
#### Example request
|
||||
|
||||
```http
|
||||
DELETE /api/access-control/roles/jZrmlLCGka?force=true
|
||||
DELETE /api/access-control/roles/jZrmlLCGka?force=true&global=true
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
|
||||
@@ -612,9 +612,9 @@ Only works with Basic Authentication (username and password). See [introduction]
|
||||
|
||||
See note in the [introduction]({{< ref "#admin-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope | Provision entity |
|
||||
| ------------------- | --------------------- | ---------------- |
|
||||
| provisioning:reload | service:accesscontrol | accesscontrol |
|
||||
| Action | Scope | Provision entity |
|
||||
| ------------------- | ---------------------- | ---------------- |
|
||||
| provisioning:reload | services:accesscontrol | accesscontrol |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ weight = 10000
|
||||
Here you can find detailed release notes that list everything that is included in every release as well as notices
|
||||
about deprecations, breaking changes as well as changes that relate to plugin development.
|
||||
|
||||
- [Release notes for 8.1.1]({{< relref "release-notes-8-1-1" >}})
|
||||
- [Release notes for 8.1.0]({{< relref "release-notes-8-1-0" >}})
|
||||
- [Release notes for 8.1.0-beta3]({{< relref "release-notes-8-1-0-beta3" >}})
|
||||
- [Release notes for 8.1.0-beta2]({{< relref "release-notes-8-1-0-beta2" >}})
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
+++
|
||||
title = "Release notes for Grafana 8.1.1"
|
||||
[_build]
|
||||
list = false
|
||||
+++
|
||||
|
||||
<!-- Auto generated by update changelog github action -->
|
||||
|
||||
# Release notes for Grafana 8.1.1
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* **CloudWatch Logs:** Fix crash when no region is selected. [#37639](https://github.com/grafana/grafana/pull/37639), [@aocenas](https://github.com/aocenas)
|
||||
|
||||
@@ -62,7 +62,7 @@ If you created a snapshot by mistake, click **delete snapshot** to remove the sn
|
||||
|
||||
You can embed a panel using an iframe on another web site. A viewer must be signed into Grafana to view the graph.
|
||||
|
||||
**> Note:** As of Grafana 8.0, anonymous access permission is no available for Grafana Cloud.
|
||||
**> Note:** As of Grafana 8.0, anonymous access permission is no longer available for Grafana Cloud.
|
||||
|
||||

|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "8.1.0",
|
||||
"testing": "8.1.0"
|
||||
"stable": "8.1.1",
|
||||
"testing": "8.1.1"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Field, FieldType } from '../types/dataFrame';
|
||||
import { DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats';
|
||||
import { getValueMappingResult } from '../utils/valueMappings';
|
||||
import { dateTime } from '../datetime';
|
||||
import { dateTime, dateTimeParse } from '../datetime';
|
||||
import { KeyValue, TimeZone } from '../types';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { GrafanaTheme2 } from '../themes/types';
|
||||
@@ -45,10 +45,23 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
|
||||
let unit = config.unit;
|
||||
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
|
||||
let showMs = false;
|
||||
|
||||
if (field.type === FieldType.time && !hasDateUnit) {
|
||||
unit = `dateTimeAsSystem`;
|
||||
hasDateUnit = true;
|
||||
if (field.values && field.values.length > 1) {
|
||||
let start = field.values.get(0);
|
||||
let end = field.values.get(field.values.length - 1);
|
||||
if (typeof start === 'string') {
|
||||
start = dateTimeParse(start).unix();
|
||||
end = dateTimeParse(end).unix();
|
||||
} else {
|
||||
start /= 1e3;
|
||||
end /= 1e3;
|
||||
}
|
||||
showMs = end - start < 60; //show ms when minute or less
|
||||
}
|
||||
} else if (field.type === FieldType.boolean) {
|
||||
if (!isBooleanUnit(unit)) {
|
||||
unit = 'bool';
|
||||
@@ -93,7 +106,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
|
||||
if (!isNaN(numeric)) {
|
||||
if (shouldFormat && !isBoolean(value)) {
|
||||
const v = formatFunc(numeric, config.decimals, null, options.timeZone);
|
||||
const v = formatFunc(numeric, config.decimals, null, options.timeZone, showMs);
|
||||
text = v.text;
|
||||
suffix = v.suffix;
|
||||
prefix = v.prefix;
|
||||
|
||||
@@ -4,28 +4,25 @@ import { toDataFrame } from '../dataframe';
|
||||
import { createTheme } from '../themes';
|
||||
|
||||
describe('getFieldDisplayValuesProxy', () => {
|
||||
const data = applyFieldOverrides({
|
||||
data: [
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', values: [1, 2, 3] },
|
||||
{
|
||||
name: 'power',
|
||||
values: [100, 200, 300],
|
||||
labels: {
|
||||
name: 'POWAH!',
|
||||
},
|
||||
config: {
|
||||
displayName: 'The Power',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Last',
|
||||
values: ['a', 'b', 'c'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
const shortTimeField = [{ name: 'Time', values: [1, 2, 3] }];
|
||||
|
||||
const longTimeField = [{ name: 'Time', values: [1000, 2000, 61000] }];
|
||||
|
||||
const dataFields = [
|
||||
{
|
||||
name: 'power',
|
||||
values: [100, 200, 300],
|
||||
labels: {
|
||||
name: 'POWAH!',
|
||||
},
|
||||
config: {
|
||||
displayName: 'The Power',
|
||||
},
|
||||
},
|
||||
{ name: 'Last', values: ['a', 'b', 'c'] },
|
||||
];
|
||||
|
||||
const overrides = {
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
@@ -33,29 +30,45 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
replaceVariables: (val: string) => val,
|
||||
timeZone: 'utc',
|
||||
theme: createTheme(),
|
||||
};
|
||||
|
||||
const dataShortTimeRange = applyFieldOverrides({
|
||||
...{ data: [toDataFrame({ fields: [...shortTimeField, ...dataFields] })] },
|
||||
...overrides,
|
||||
})[0];
|
||||
|
||||
const dataLongTimeRange = applyFieldOverrides({
|
||||
...{ data: [toDataFrame({ fields: [...longTimeField, ...dataFields] })] },
|
||||
...overrides,
|
||||
})[0];
|
||||
|
||||
it('should define all display functions', () => {
|
||||
// Field display should be set
|
||||
for (const field of data.fields) {
|
||||
for (const field of dataShortTimeRange.fields) {
|
||||
expect(field.display).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should format the time values in UTC', () => {
|
||||
it('should format the time values in UTC with ms when time range is minute or less', () => {
|
||||
// Test Proxies in general
|
||||
const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 0 });
|
||||
const p = getFieldDisplayValuesProxy({ frame: dataShortTimeRange, rowIndex: 0 });
|
||||
const time = p.Time;
|
||||
expect(time.numeric).toEqual(1);
|
||||
expect(time.text).toEqual('1970-01-01 00:00:00');
|
||||
expect(time.text).toEqual('1970-01-01 00:00:00.001');
|
||||
|
||||
// Should get to the same values by name or index
|
||||
const time2 = p[0];
|
||||
expect(time2.toString()).toEqual(time.toString());
|
||||
});
|
||||
|
||||
it('should format the time values in UTC without ms when time range is over a minute', () => {
|
||||
const p = getFieldDisplayValuesProxy({ frame: dataLongTimeRange, rowIndex: 0 });
|
||||
const time = p.Time;
|
||||
expect(time.text).toEqual('1970-01-01 00:00:01');
|
||||
});
|
||||
|
||||
it('Lookup by name, index, or displayName', () => {
|
||||
const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 2 });
|
||||
const p = getFieldDisplayValuesProxy({ frame: dataShortTimeRange, rowIndex: 2 });
|
||||
expect(p.power.numeric).toEqual(300);
|
||||
expect(p['power'].numeric).toEqual(300);
|
||||
expect(p['POWAH!'].numeric).toEqual(300);
|
||||
@@ -64,7 +77,7 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
});
|
||||
|
||||
it('should return undefined when missing', () => {
|
||||
const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 0 });
|
||||
const p = getFieldDisplayValuesProxy({ frame: dataShortTimeRange, rowIndex: 0 });
|
||||
expect(p.xyz).toBeUndefined();
|
||||
expect(p[100]).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { DataQuery, QueryEditorProps } from './datasource';
|
||||
import { QueryEditorProps } from './datasource';
|
||||
import { DataFrame } from './dataFrame';
|
||||
import { DataQuery, DatasourceRef } from './query';
|
||||
|
||||
/**
|
||||
* This JSON object is stored in the dashboard json model.
|
||||
*/
|
||||
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
|
||||
datasource?: string | null;
|
||||
datasource?: DatasourceRef | string | null;
|
||||
|
||||
enable: boolean;
|
||||
name: string;
|
||||
iconColor: string;
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
import { FieldConfigSource } from './fieldOverrides';
|
||||
import { DataQuery, DatasourceRef } from './query';
|
||||
|
||||
export enum DashboardCursorSync {
|
||||
Off,
|
||||
Crosshair,
|
||||
Tooltip,
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface PanelModel<TOptions = any, TCustomFieldConfig extends object = any> {
|
||||
/** ID of the panel within the current dashboard */
|
||||
id: number;
|
||||
|
||||
/** Panel title */
|
||||
title?: string;
|
||||
|
||||
/** Description */
|
||||
description?: string;
|
||||
|
||||
/** Panel options */
|
||||
options: TOptions;
|
||||
|
||||
/** Field options configuration */
|
||||
fieldConfig: FieldConfigSource<TCustomFieldConfig>;
|
||||
|
||||
/** Version of the panel plugin */
|
||||
pluginVersion?: string;
|
||||
|
||||
/** The datasource used in all targets */
|
||||
datasource?: DatasourceRef | null;
|
||||
|
||||
/** The queries in a panel */
|
||||
targets?: DataQuery[];
|
||||
|
||||
/** alerting v1 object */
|
||||
alert?: any;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FieldConfig } from './dataFrame';
|
||||
import { DataTransformerConfig } from './transformations';
|
||||
import { ApplyFieldOverrideOptions } from './fieldOverrides';
|
||||
import { PanelPluginDataSupport } from '.';
|
||||
import { DataTopic } from './query';
|
||||
|
||||
export type KeyValue<T = any> = Record<string, T>;
|
||||
|
||||
@@ -17,10 +18,6 @@ export enum LoadingState {
|
||||
Error = 'Error',
|
||||
}
|
||||
|
||||
export enum DataTopic {
|
||||
Annotations = 'annotations',
|
||||
}
|
||||
|
||||
// Should be kept in sync with grafana-plugin-sdk-go/data/frame_meta.go
|
||||
export type PreferredVisualisationType = 'graph' | 'table' | 'logs' | 'trace' | 'nodeGraph';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DataQuery } from './datasource';
|
||||
import { DataQuery } from './query';
|
||||
import { InterpolateFunction } from './panel';
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import { GrafanaPlugin, PluginMeta } from './plugin';
|
||||
import { PanelData } from './panel';
|
||||
import { LogRowModel } from './logs';
|
||||
import { AnnotationEvent, AnnotationQuery, AnnotationSupport } from './annotations';
|
||||
import { DataTopic, KeyValue, LoadingState, TableData, TimeSeries } from './data';
|
||||
import { KeyValue, LoadingState, TableData, TimeSeries } from './data';
|
||||
import { DataFrame, DataFrameDTO } from './dataFrame';
|
||||
import { RawTimeRange, TimeRange } from './time';
|
||||
import { ScopedVars } from './ScopedVars';
|
||||
@@ -12,6 +12,7 @@ import { CoreApp } from './app';
|
||||
import { LiveChannelSupport } from './live';
|
||||
import { CustomVariableSupport, DataSourceVariableSupport, StandardVariableSupport } from './variables';
|
||||
import { makeClassES5Compatible } from '../utils/makeClassES5Compatible';
|
||||
import { DataQuery } from './query';
|
||||
|
||||
export interface DataSourcePluginOptionsEditorProps<JSONData = DataSourceJsonData, SecureJSONData = {}> {
|
||||
options: DataSourceSettings<JSONData, SecureJSONData>;
|
||||
@@ -205,6 +206,9 @@ abstract class DataSourceApi<
|
||||
this.type = instanceSettings.type;
|
||||
this.meta = {} as DataSourcePluginMeta;
|
||||
this.uid = instanceSettings.uid;
|
||||
if (!this.uid) {
|
||||
this.uid = this.name; // Internal datasources do not have a UID (-- Grafana --)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,44 +434,6 @@ export interface DataQueryResponse {
|
||||
state?: LoadingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* These are the common properties available to all queries in all datasources
|
||||
* Specific implementations will extend this interface adding the required properties
|
||||
* for the given context
|
||||
*/
|
||||
export interface DataQuery {
|
||||
/**
|
||||
* A - Z
|
||||
*/
|
||||
refId: string;
|
||||
|
||||
/**
|
||||
* true if query is disabled (ie should not be returned to the dashboard)
|
||||
*/
|
||||
hide?: boolean;
|
||||
|
||||
/**
|
||||
* Unique, guid like, string used in explore mode
|
||||
*/
|
||||
key?: string;
|
||||
|
||||
/**
|
||||
* Specify the query flavor
|
||||
*/
|
||||
queryType?: string;
|
||||
|
||||
/**
|
||||
* The data topic results should be attached to
|
||||
*/
|
||||
dataTopic?: DataTopic;
|
||||
|
||||
/**
|
||||
* For mixed data sources the selected datasource is on the query level.
|
||||
* For non mixed scenarios this is undefined.
|
||||
*/
|
||||
datasource?: string | null;
|
||||
}
|
||||
|
||||
export enum DataQueryErrorType {
|
||||
Cancelled = 'cancelled',
|
||||
Timeout = 'timeout',
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './data';
|
||||
export * from './dataFrame';
|
||||
export * from './dataLink';
|
||||
export * from './dashboard';
|
||||
export * from './query';
|
||||
export * from './annotations';
|
||||
export * from './logs';
|
||||
export * from './navModel';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Labels } from './data';
|
||||
import { DataFrame } from './dataFrame';
|
||||
import { DataQuery } from './query';
|
||||
import { AbsoluteTimeRange } from './time';
|
||||
import { DataQuery } from './datasource';
|
||||
|
||||
/**
|
||||
* Mapping of log level abbreviation to canonical log level.
|
||||
|
||||
@@ -11,6 +11,7 @@ import { StandardEditorProps } from '../field';
|
||||
import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
|
||||
import { OptionEditorConfig } from './options';
|
||||
import { AlertStateInfo } from './alerts';
|
||||
import { PanelModel } from './dashboard';
|
||||
|
||||
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
|
||||
|
||||
@@ -121,19 +122,6 @@ export interface PanelEditorProps<T = any> {
|
||||
data?: PanelData;
|
||||
}
|
||||
|
||||
export interface PanelModel<TOptions = any> {
|
||||
/** ID of the panel within the current dashboard */
|
||||
id: number;
|
||||
alert?: any;
|
||||
/** Panel options */
|
||||
options: TOptions;
|
||||
/** Field options configuration */
|
||||
fieldConfig: FieldConfigSource;
|
||||
/** Version of the panel plugin */
|
||||
pluginVersion?: string;
|
||||
scopedVars?: ScopedVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a panel is first loaded with current panel model
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Attached to query results (not persisted)
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum DataTopic {
|
||||
Annotations = 'annotations',
|
||||
}
|
||||
|
||||
/**
|
||||
* In 8.2, this will become an interface
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type DatasourceRef = string;
|
||||
|
||||
/**
|
||||
* These are the common properties available to all queries in all datasources
|
||||
* Specific implementations will *extend* this interface adding the required properties
|
||||
* for the given context
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface DataQuery {
|
||||
/**
|
||||
* A - Z
|
||||
*/
|
||||
refId: string;
|
||||
|
||||
/**
|
||||
* true if query is disabled (ie should not be returned to the dashboard)
|
||||
*/
|
||||
hide?: boolean;
|
||||
|
||||
/**
|
||||
* Unique, guid like, string used in explore mode
|
||||
*/
|
||||
key?: string;
|
||||
|
||||
/**
|
||||
* Specify the query flavor
|
||||
*/
|
||||
queryType?: string;
|
||||
|
||||
/**
|
||||
* For mixed data sources the selected datasource is on the query level.
|
||||
* For non mixed scenarios this is undefined.
|
||||
*/
|
||||
datasource?: DatasourceRef;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { DataQuery, DataSourceApi } from './datasource';
|
||||
import { DataQuery, DatasourceRef } from './query';
|
||||
import { DataSourceApi } from './datasource';
|
||||
import { PanelData } from './panel';
|
||||
import { ScopedVars } from './ScopedVars';
|
||||
import { TimeRange, TimeZone } from './time';
|
||||
@@ -10,7 +11,7 @@ import { TimeRange, TimeZone } from './time';
|
||||
* @internal
|
||||
*/
|
||||
export interface QueryRunnerOptions {
|
||||
datasource: string | DataSourceApi | null;
|
||||
datasource: DatasourceRef | DataSourceApi | null;
|
||||
queries: DataQuery[];
|
||||
panelId?: number;
|
||||
dashboardId?: number;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DataQuery } from './query';
|
||||
|
||||
import {
|
||||
DataQuery,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
|
||||
@@ -405,9 +405,15 @@ export function dateTimeSystemFormatter(
|
||||
value: number,
|
||||
decimals: DecimalCount,
|
||||
scaledDecimals: DecimalCount,
|
||||
timeZone?: TimeZone
|
||||
timeZone?: TimeZone,
|
||||
showMs?: boolean
|
||||
): FormattedValue {
|
||||
return { text: dateTimeFormat(value, { format: systemDateFormats.fullDate, timeZone }) };
|
||||
return {
|
||||
text: dateTimeFormat(value, {
|
||||
format: showMs ? systemDateFormats.fullDateMS : systemDateFormats.fullDate,
|
||||
timeZone,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function dateTimeFromNow(
|
||||
|
||||
@@ -18,7 +18,8 @@ export type ValueFormatter = (
|
||||
value: number,
|
||||
decimals?: DecimalCount,
|
||||
scaledDecimals?: DecimalCount,
|
||||
timeZone?: TimeZone
|
||||
timeZone?: TimeZone,
|
||||
showMs?: boolean
|
||||
) => FormattedValue;
|
||||
|
||||
export interface ValueFormat {
|
||||
|
||||
@@ -213,9 +213,9 @@ export const Components = {
|
||||
items: 'Search items',
|
||||
},
|
||||
DashboardLinks: {
|
||||
container: 'Dashboard link container',
|
||||
dropDown: 'Dashboard link dropdown',
|
||||
link: 'Dashboard link',
|
||||
container: 'data-testid Dashboard link container',
|
||||
dropDown: 'data-testid Dashboard link dropdown',
|
||||
link: 'data-testid Dashboard link',
|
||||
},
|
||||
LoadingIndicator: {
|
||||
icon: 'Loading indicator',
|
||||
|
||||
@@ -50,7 +50,7 @@ export const jestConfig = (baseDir: string = process.cwd()) => {
|
||||
const setupFile = getSetupFile(setupFilePath);
|
||||
const shimsFile = getSetupFile(shimsFilePath);
|
||||
|
||||
const setupFiles = [setupFile, shimsFile, 'jest-canvas-mock'].filter((f) => f);
|
||||
const setupFiles = [setupFile, shimsFile, `${__dirname}/matchMedia.js`, 'jest-canvas-mock'].filter((f) => f);
|
||||
const defaultJestConfig = {
|
||||
preset: 'ts-jest',
|
||||
verbose: false,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||
Object.defineProperty(global, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
+1
-1
@@ -52,7 +52,7 @@ export const TimeRangeOption = memo<Props>(({ value, onSelect, selected = false,
|
||||
const id = uuidv4();
|
||||
|
||||
return (
|
||||
<li className={cx(styles.container, selected && styles.selected)}>
|
||||
<li onClick={() => onSelect(value)} className={cx(styles.container, selected && styles.selected)}>
|
||||
<input
|
||||
className={styles.radio}
|
||||
checked={selected}
|
||||
|
||||
@@ -21,3 +21,7 @@ export default {
|
||||
export const Basic: Story<FileDropzoneProps> = (args) => {
|
||||
return <FileDropzone {...args} />;
|
||||
};
|
||||
|
||||
export const WithCustomFileList: Story<FileDropzoneProps> = () => {
|
||||
return <FileDropzone fileListRenderer={(file) => <div>Custom rendered item {file.file.name}</div>} />;
|
||||
};
|
||||
|
||||
@@ -107,6 +107,17 @@ describe('The FileDropzone component', () => {
|
||||
|
||||
screen.getByText('Custom dropzone text');
|
||||
});
|
||||
|
||||
it('should handle file list overwrite when fileListRenderer is passed', async () => {
|
||||
render(<FileDropzone fileListRenderer={() => null} />);
|
||||
|
||||
dispatchEvt(screen.getByTestId('dropzone'), 'drop', mockData([file({})]));
|
||||
|
||||
// need to await this in order to have the drop finished
|
||||
await screen.findByTestId('dropzone');
|
||||
|
||||
expect(screen.queryByText('ping.json')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
function dispatchEvt(node: HTMLElement, type: string, data: any) {
|
||||
|
||||
@@ -30,6 +30,11 @@ export interface FileDropzoneProps {
|
||||
* Use the onLoad function to get the result from FileReader.
|
||||
*/
|
||||
onLoad?: (result: string | ArrayBuffer | null) => void;
|
||||
/**
|
||||
* The fileListRenderer property can be used to overwrite the list of files. To not to show
|
||||
* any list return null in the function.
|
||||
*/
|
||||
fileListRenderer?: (file: DropzoneFile, removeFile: (file: DropzoneFile) => void) => ReactNode;
|
||||
}
|
||||
|
||||
export interface DropzoneFile {
|
||||
@@ -41,7 +46,7 @@ export interface DropzoneFile {
|
||||
retryUpload?: () => void;
|
||||
}
|
||||
|
||||
export function FileDropzone({ options, children, readAs, onLoad }: FileDropzoneProps) {
|
||||
export function FileDropzone({ options, children, readAs, onLoad, fileListRenderer }: FileDropzoneProps) {
|
||||
const [files, setFiles] = useState<DropzoneFile[]>([]);
|
||||
|
||||
const setFileProperty = useCallback(
|
||||
@@ -133,7 +138,12 @@ export function FileDropzone({ options, children, readAs, onLoad }: FileDropzone
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({ ...options, onDrop });
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme, isDragActive);
|
||||
const fileList = files.map((file) => <FileListItem key={file.id} file={file} removeFile={removeFile} />);
|
||||
const fileList = files.map((file) => {
|
||||
if (fileListRenderer) {
|
||||
return fileListRenderer(file, removeFile);
|
||||
}
|
||||
return <FileListItem key={file.id} file={file} removeFile={removeFile} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('The FileListItem component', () => {
|
||||
it('should show a progressbar when the progress prop has a value', () => {
|
||||
render(<FileListItem file={{ file: file({}), id: '1', error: null, progress: 6 }} />);
|
||||
|
||||
expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Cancel upload')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('46%')).toBeInTheDocument();
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
@@ -45,7 +45,7 @@ describe('The FileListItem component', () => {
|
||||
const abortUpload = jest.fn();
|
||||
render(<FileListItem file={{ file: file({}), id: '1', error: null, progress: 6, abortUpload }} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
|
||||
|
||||
expect(abortUpload).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -24,11 +24,19 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
<>
|
||||
<span className={styles.error}>{error.message}</span>
|
||||
{retryUpload && (
|
||||
<IconButton aria-label="Retry" name="sync" tooltip="Retry" tooltipPlacement="top" onClick={retryUpload} />
|
||||
<IconButton
|
||||
type="button"
|
||||
aria-label="Retry"
|
||||
name="sync"
|
||||
tooltip="Retry"
|
||||
tooltipPlacement="top"
|
||||
onClick={retryUpload}
|
||||
/>
|
||||
)}
|
||||
{removeFile && (
|
||||
<IconButton
|
||||
className={retryUpload ? styles.marginLeft : ''}
|
||||
type="button"
|
||||
name="trash-alt"
|
||||
onClick={() => removeFile(customFile)}
|
||||
tooltip={REMOVE_FILE}
|
||||
@@ -46,7 +54,7 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
<span className={styles.paddingLeft}>{Math.round((progress / file.size) * 100)}%</span>
|
||||
{abortUpload && (
|
||||
<Button variant="secondary" type="button" fill="text" onClick={abortUpload}>
|
||||
Cancel
|
||||
Cancel upload
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -59,6 +67,7 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
onClick={() => removeFile(customFile)}
|
||||
tooltip={REMOVE_FILE}
|
||||
aria-label={REMOVE_FILE}
|
||||
type="button"
|
||||
tooltipPlacement="top"
|
||||
/>
|
||||
)
|
||||
@@ -70,7 +79,7 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
return (
|
||||
<div className={styles.fileListContainer}>
|
||||
<span className={styles.fileNameWrapper}>
|
||||
<Icon name="file-blank" size="lg" />
|
||||
<Icon name="file-blank" size="lg" aria-hidden={true} />
|
||||
<span className={styles.padding}>{trimFileName(file.name)}</span>
|
||||
<span>{formattedValueToString(valueFormat)}</span>
|
||||
</span>
|
||||
|
||||
@@ -45,11 +45,11 @@ function passThroughFieldOverrides(frame: DataFrame) {
|
||||
const aSeries = passThroughFieldOverrides(
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [10000, 20000, 30000] },
|
||||
{ name: 'time', type: FieldType.time, values: [10000, 20000, 30000, 80000] },
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
values: [10, 20, 10],
|
||||
values: [10, 20, 10, 25],
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'red' } },
|
||||
},
|
||||
],
|
||||
@@ -59,11 +59,11 @@ const aSeries = passThroughFieldOverrides(
|
||||
const bSeries = passThroughFieldOverrides(
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [10000, 20000, 30000] },
|
||||
{ name: 'time', type: FieldType.time, values: [10000, 20000, 30000, 80000] },
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
values: [30, 60, 30],
|
||||
values: [30, 60, 30, 40],
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'blue' } },
|
||||
},
|
||||
],
|
||||
@@ -74,11 +74,11 @@ const bSeries = passThroughFieldOverrides(
|
||||
const cSeries = passThroughFieldOverrides(
|
||||
toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [10000, 30000] },
|
||||
{ name: 'time', type: FieldType.time, values: [10000, 30000, 80000] },
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
values: [30, 30],
|
||||
values: [30, 30, 30],
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'yellow' } },
|
||||
},
|
||||
],
|
||||
|
||||
@@ -4,7 +4,7 @@ import { css, cx } from '@emotion/css';
|
||||
|
||||
import { Alert } from '../Alert/Alert';
|
||||
import { LogRowContextRows, LogRowContextQueryErrors, HasMoreContextRows } from './LogRowContextProvider';
|
||||
import { useStyles } from '../../themes/ThemeContext';
|
||||
import { useStyles, useTheme } from '../../themes/ThemeContext';
|
||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
||||
import { List } from '../List/List';
|
||||
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
|
||||
@@ -13,18 +13,41 @@ import { LogMessageAnsi } from './LogMessageAnsi';
|
||||
interface LogRowContextProps {
|
||||
row: LogRowModel;
|
||||
context: LogRowContextRows;
|
||||
wrapLogMessage: boolean;
|
||||
errors?: LogRowContextQueryErrors;
|
||||
hasMoreContextRows?: HasMoreContextRows;
|
||||
onOutsideClick: () => void;
|
||||
onLoadMoreContext: () => void;
|
||||
}
|
||||
|
||||
const getLogRowContextStyles = (theme: GrafanaTheme) => {
|
||||
const getLogRowContextStyles = (theme: GrafanaTheme, wrapLogMessage?: boolean) => {
|
||||
/**
|
||||
* This is workaround for displaying uncropped context when we have unwrapping log messages.
|
||||
* We are using margins to correctly position context. Because non-wrapped logs have always 1 line of log
|
||||
* and 1 line of Show/Hide context switch. Therefore correct position can be reliably achieved by margins.
|
||||
* We also adjust width to 75%.
|
||||
*/
|
||||
|
||||
const afterContext = wrapLogMessage
|
||||
? css`
|
||||
top: -250px;
|
||||
`
|
||||
: css`
|
||||
margin-top: -250px;
|
||||
width: 75%;
|
||||
`;
|
||||
|
||||
const beforeContext = wrapLogMessage
|
||||
? css`
|
||||
top: 100%;
|
||||
`
|
||||
: css`
|
||||
margin-top: 40px;
|
||||
width: 75%;
|
||||
`;
|
||||
return {
|
||||
commonStyles: css`
|
||||
position: absolute;
|
||||
width: calc(100% + 20px);
|
||||
left: -13px;
|
||||
height: 250px;
|
||||
z-index: ${theme.zIndex.dropdown};
|
||||
overflow: hidden;
|
||||
@@ -32,6 +55,7 @@ const getLogRowContextStyles = (theme: GrafanaTheme) => {
|
||||
box-shadow: 0 0 10px ${theme.colors.dropdownShadow};
|
||||
border: 1px solid ${theme.colors.bg2};
|
||||
border-radius: ${theme.border.radius.md};
|
||||
width: 100%;
|
||||
`,
|
||||
header: css`
|
||||
height: 30px;
|
||||
@@ -44,6 +68,8 @@ const getLogRowContextStyles = (theme: GrafanaTheme) => {
|
||||
height: 220px;
|
||||
padding: 10px;
|
||||
`,
|
||||
afterContext,
|
||||
beforeContext,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -56,7 +82,7 @@ interface LogRowContextGroupHeaderProps {
|
||||
}
|
||||
interface LogRowContextGroupProps extends LogRowContextGroupHeaderProps {
|
||||
rows: Array<string | DataQueryError>;
|
||||
className: string;
|
||||
className?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@@ -122,11 +148,11 @@ export const LogRowContextGroup: React.FunctionComponent<LogRowContextGroupProps
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(className, commonStyles)}>
|
||||
<div className={cx(commonStyles, className)}>
|
||||
{/* When displaying "after" context */}
|
||||
{shouldScrollToBottom && !error && <LogRowContextGroupHeader {...headerProps} />}
|
||||
<div className={logs}>
|
||||
<CustomScrollbar autoHide scrollTop={scrollTop}>
|
||||
<CustomScrollbar autoHide scrollTop={scrollTop} autoHeightMin={'210px'}>
|
||||
<div ref={listContainerRef}>
|
||||
{!error && (
|
||||
<List
|
||||
@@ -161,6 +187,7 @@ export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
|
||||
onOutsideClick,
|
||||
onLoadMoreContext,
|
||||
hasMoreContextRows,
|
||||
wrapLogMessage,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const handleEscKeyDown = (e: KeyboardEvent): void => {
|
||||
@@ -173,6 +200,8 @@ export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
|
||||
document.removeEventListener('keydown', handleEscKeyDown, false);
|
||||
};
|
||||
}, [onOutsideClick]);
|
||||
const theme = useTheme();
|
||||
const { afterContext, beforeContext } = getLogRowContextStyles(theme, wrapLogMessage);
|
||||
|
||||
return (
|
||||
<ClickOutsideWrapper onClick={onOutsideClick}>
|
||||
@@ -184,9 +213,7 @@ export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
|
||||
rows={context.after}
|
||||
error={errors && errors.after}
|
||||
row={row}
|
||||
className={css`
|
||||
top: -250px;
|
||||
`}
|
||||
className={afterContext}
|
||||
shouldScrollToBottom
|
||||
canLoadMoreRows={hasMoreContextRows ? hasMoreContextRows.after : false}
|
||||
onLoadMoreContext={onLoadMoreContext}
|
||||
@@ -200,9 +227,7 @@ export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
|
||||
row={row}
|
||||
rows={context.before}
|
||||
error={errors && errors.before}
|
||||
className={css`
|
||||
top: 100%;
|
||||
`}
|
||||
className={beforeContext}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
label: verticalScroll;
|
||||
white-space: pre;
|
||||
`,
|
||||
contextNewline: css`
|
||||
display: block;
|
||||
margin-left: 0px;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -124,12 +128,15 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<td className={style.logsRowMessage}>
|
||||
<div className={cx(styles.positionRelative, { [styles.horizontalScroll]: !wrapLogMessage })}>
|
||||
<div
|
||||
className={cx({ [styles.positionRelative]: wrapLogMessage }, { [styles.horizontalScroll]: !wrapLogMessage })}
|
||||
>
|
||||
{contextIsOpen && context && (
|
||||
<LogRowContext
|
||||
row={row}
|
||||
context={context}
|
||||
errors={errors}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
hasMoreContextRows={hasMoreContextRows}
|
||||
onOutsideClick={onToggleContext}
|
||||
onLoadMoreContext={() => {
|
||||
@@ -143,7 +150,10 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
|
||||
{renderLogMessage(hasAnsi, restructuredEntry, highlights, highlightClassName)}
|
||||
</span>
|
||||
{showContextToggle?.(row) && (
|
||||
<span onClick={this.onContextToggle} className={cx('log-row-context', style.context)}>
|
||||
<span
|
||||
onClick={this.onContextToggle}
|
||||
className={cx('log-row-context', style.context, { [styles.contextNewline]: !wrapLogMessage })}
|
||||
>
|
||||
{contextIsOpen ? 'Hide' : 'Show'} context
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -48,13 +48,13 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
font-family: ${theme.typography.fontFamily.monospace};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
`,
|
||||
context: css`
|
||||
label: context;
|
||||
visibility: hidden;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
margin-left: 10px;
|
||||
`,
|
||||
logsRow: css`
|
||||
label: logs-row;
|
||||
@@ -66,7 +66,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
.log-row-context {
|
||||
visibility: visible;
|
||||
z-index: 1;
|
||||
margin-left: 10px;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
color: ${theme.palette.yellow};
|
||||
|
||||
@@ -50,9 +50,12 @@ AxisConfig: {
|
||||
HideSeriesConfig: {
|
||||
tooltip: bool
|
||||
legend: bool
|
||||
graph: bool
|
||||
viz: bool
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
// TODO This is the same composition as what's used in the timeseries panel's
|
||||
// PanelFieldConfig. If that's the only place it's used, it probably shouldn't
|
||||
// be assembled here, too
|
||||
GraphFieldConfig: LineConfig & FillConfig & PointsConfig & AxisConfig & {
|
||||
drawStyle?: DrawStyle
|
||||
gradientMode?: GraphGradientMode
|
||||
|
||||
@@ -118,7 +118,7 @@ func (a *CacheServer) Handler(ctx *models.ReqContext) {
|
||||
|
||||
if err := avatar.Encode(ctx.Resp); err != nil {
|
||||
log.Warnf("avatar encode error: %v", err)
|
||||
ctx.WriteHeader(500)
|
||||
ctx.Resp.WriteHeader(500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,10 +217,7 @@ func setupScenarioContext(t *testing.T, url string) *scenarioContext {
|
||||
require.Truef(t, exists, "Views should be in %q", viewsPath)
|
||||
|
||||
sc.m = macaron.New()
|
||||
sc.m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: viewsPath,
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
sc.m.UseMiddleware(macaron.Renderer(viewsPath, "[[", "]]"))
|
||||
sc.m.Use(getContextHandler(t, cfg).Middleware)
|
||||
|
||||
return sc
|
||||
|
||||
@@ -58,11 +58,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg) (*macaron.Macaron, *HT
|
||||
|
||||
m := macaron.New()
|
||||
m.Use(getContextHandler(t, cfg).Middleware)
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: filepath.Join(setting.StaticRootPath, "views"),
|
||||
IndentJSON: true,
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
m.UseMiddleware(macaron.Renderer(filepath.Join(setting.StaticRootPath, "views"), "[[", "]]"))
|
||||
m.Get("/api/frontend/settings/", hs.GetFrontendSettings)
|
||||
|
||||
return m, hs
|
||||
|
||||
@@ -335,7 +335,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
||||
m.Use(middleware.Logger(hs.Cfg))
|
||||
|
||||
if hs.Cfg.EnableGzip {
|
||||
m.Use(middleware.Gziper())
|
||||
m.UseMiddleware(middleware.Gziper())
|
||||
}
|
||||
|
||||
m.Use(middleware.Recovery(hs.Cfg))
|
||||
@@ -354,11 +354,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
||||
m.SetURLPrefix(hs.Cfg.AppSubURL)
|
||||
}
|
||||
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: filepath.Join(hs.Cfg.StaticRootPath, "views"),
|
||||
IndentJSON: macaron.Env != macaron.PROD,
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
m.UseMiddleware(macaron.Renderer(filepath.Join(hs.Cfg.StaticRootPath, "views"), "[[", "]]"))
|
||||
|
||||
// These endpoints are used for monitoring the Grafana instance
|
||||
// and should not be redirected or rejected.
|
||||
@@ -375,7 +371,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
||||
}
|
||||
|
||||
m.Use(middleware.HandleNoCacheHeader)
|
||||
m.Use(middleware.AddCSPHeader(hs.Cfg, hs.log))
|
||||
m.UseMiddleware(middleware.AddCSPHeader(hs.Cfg, hs.log))
|
||||
|
||||
for _, mw := range hs.middlewares {
|
||||
m.Use(mw)
|
||||
@@ -408,7 +404,7 @@ func (hs *HTTPServer) healthzHandler(ctx *macaron.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.WriteHeader(200)
|
||||
ctx.Resp.WriteHeader(200)
|
||||
_, err := ctx.Resp.Write([]byte("Ok"))
|
||||
if err != nil {
|
||||
hs.log.Error("could not write to response", "err", err)
|
||||
|
||||
+18
-9
@@ -21,6 +21,14 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var permittedFileExts = []string{
|
||||
".html", ".xhtml", ".css", ".js", ".json", ".jsonld", ".map", ".mjs",
|
||||
".jpeg", ".jpg", ".png", ".gif", ".svg", ".webp", ".ico",
|
||||
".woff", ".woff2", ".eot", ".ttf", ".otf",
|
||||
".wav", ".mp3",
|
||||
".md", ".pdf", ".txt",
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
|
||||
typeFilter := c.Query("type")
|
||||
enabledFilter := c.Query("enabled")
|
||||
@@ -292,9 +300,9 @@ func (hs *HTTPServer) GetPluginAssets(c *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if shouldExclude(fi) {
|
||||
if accessForbidden(fi.Name()) {
|
||||
c.JsonApiErr(403, "Plugin file access forbidden",
|
||||
fmt.Errorf("access is forbidden to executable plugin file %s", pluginFilePath))
|
||||
fmt.Errorf("access is forbidden to plugin file %s", pluginFilePath))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -441,12 +449,13 @@ func translatePluginRequestErrorToAPIError(err error) response.Response {
|
||||
return response.Error(500, "Plugin request failed", err)
|
||||
}
|
||||
|
||||
func shouldExclude(fi os.FileInfo) bool {
|
||||
normalizedFilename := strings.ToLower(fi.Name())
|
||||
func accessForbidden(pluginFilename string) bool {
|
||||
ext := filepath.Ext(pluginFilename)
|
||||
|
||||
isUnixExecutable := fi.Mode()&0111 == 0111
|
||||
isWindowsExecutable := strings.HasSuffix(normalizedFilename, ".exe")
|
||||
isScript := strings.HasSuffix(normalizedFilename, ".sh")
|
||||
|
||||
return isUnixExecutable || isWindowsExecutable || isScript
|
||||
for _, permittedExt := range permittedFileExts {
|
||||
if strings.EqualFold(permittedExt, ext) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_accessForbidden(t *testing.T) {
|
||||
type testCase struct {
|
||||
filename string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t testCase
|
||||
accessForbidden bool
|
||||
}{
|
||||
{
|
||||
name: ".exe files are forbidden",
|
||||
t: testCase{
|
||||
filename: "test.exe",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: ".sh files are forbidden",
|
||||
t: testCase{
|
||||
filename: "test.sh",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "js is not forbidden",
|
||||
t: testCase{
|
||||
|
||||
filename: "module.js",
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "logos are not forbidden",
|
||||
t: testCase{
|
||||
|
||||
filename: "logo.svg",
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "JPGs are not forbidden",
|
||||
t: testCase{
|
||||
filename: "img/test.jpg",
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "JPEGs are not forbidden",
|
||||
t: testCase{
|
||||
filename: "img/test.jpeg",
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "ext case is ignored",
|
||||
t: testCase{
|
||||
filename: "scripts/runThis.SH",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "no file ext is forbidden",
|
||||
t: testCase{
|
||||
filename: "scripts/runThis",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "empty file ext is forbidden",
|
||||
t: testCase{
|
||||
filename: "scripts/runThis.",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := accessForbidden(tt.t.filename); got != tt.accessForbidden {
|
||||
t.Errorf("accessForbidden() = %v, accessForbidden %v", got, tt.accessForbidden)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -186,7 +186,7 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques
|
||||
}
|
||||
|
||||
var floatIntervalMS float64
|
||||
if rawIntervalMS := rn.Query["intervalMs"]; ok {
|
||||
if rawIntervalMS, ok := rn.Query["intervalMs"]; ok {
|
||||
if floatIntervalMS, ok = rawIntervalMS.(float64); !ok {
|
||||
return nil, fmt.Errorf("expected intervalMs to be an float64, got type %T for refId %v", rawIntervalMS, rn.RefID)
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques
|
||||
}
|
||||
|
||||
var floatMaxDP float64
|
||||
if rawMaxDP := rn.Query["maxDataPoints"]; ok {
|
||||
if rawMaxDP, ok := rn.Query["maxDataPoints"]; ok {
|
||||
if floatMaxDP, ok = rawMaxDP.(float64); !ok {
|
||||
return nil, fmt.Errorf("expected maxDataPoints to be an float64, got type %T for refId %v", rawMaxDP, rn.RefID)
|
||||
}
|
||||
|
||||
+30
-16
@@ -15,6 +15,8 @@
|
||||
package macaron
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
@@ -44,11 +46,11 @@ type Context struct {
|
||||
index int
|
||||
|
||||
*Router
|
||||
Req Request
|
||||
Resp ResponseWriter
|
||||
params Params
|
||||
Render
|
||||
Data map[string]interface{}
|
||||
Req Request
|
||||
Resp ResponseWriter
|
||||
params Params
|
||||
template *template.Template
|
||||
Data map[string]interface{}
|
||||
}
|
||||
|
||||
func (ctx *Context) handler() Handler {
|
||||
@@ -108,19 +110,31 @@ func (ctx *Context) RemoteAddr() string {
|
||||
return addr
|
||||
}
|
||||
|
||||
func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {
|
||||
if len(data) <= 0 {
|
||||
ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)
|
||||
} else if len(data) == 1 {
|
||||
ctx.Render.HTMLSet(status, setName, tplName, data[0])
|
||||
} else {
|
||||
ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))
|
||||
const (
|
||||
headerContentType = "Content-Type"
|
||||
contentTypeJSON = "application/json; charset=UTF-8"
|
||||
contentTypeHTML = "text/html; charset=UTF-8"
|
||||
)
|
||||
|
||||
// HTML renders the HTML with default template set.
|
||||
func (ctx *Context) HTML(status int, name string, data interface{}) {
|
||||
ctx.Resp.Header().Set(headerContentType, contentTypeHTML)
|
||||
ctx.Resp.WriteHeader(status)
|
||||
if err := ctx.template.ExecuteTemplate(ctx.Resp, name, data); err != nil {
|
||||
panic("Context.HTML:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// HTML renders the HTML with default template set.
|
||||
func (ctx *Context) HTML(status int, name string, data ...interface{}) {
|
||||
ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...)
|
||||
func (ctx *Context) JSON(status int, data interface{}) {
|
||||
ctx.Resp.Header().Set(headerContentType, contentTypeJSON)
|
||||
ctx.Resp.WriteHeader(status)
|
||||
enc := json.NewEncoder(ctx.Resp)
|
||||
if Env != PROD {
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
if err := enc.Encode(data); err != nil {
|
||||
panic("Context.JSON: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect sends a redirect response
|
||||
@@ -142,7 +156,7 @@ func (ctx *Context) parseForm() {
|
||||
return
|
||||
}
|
||||
|
||||
contentType := ctx.Req.Header.Get(_CONTENT_TYPE)
|
||||
contentType := ctx.Req.Header.Get(headerContentType)
|
||||
if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&
|
||||
len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {
|
||||
_ = ctx.Req.ParseMultipartForm(MaxMemory)
|
||||
|
||||
@@ -182,7 +182,6 @@ func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Cont
|
||||
index: 0,
|
||||
Router: m.Router,
|
||||
Resp: NewResponseWriter(req.Method, rw),
|
||||
Render: &DummyRender{rw},
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
req = req.WithContext(context.WithValue(req.Context(), macaronContextKey{}, c))
|
||||
|
||||
+38
-689
@@ -16,708 +16,57 @@
|
||||
package macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
_CONTENT_TYPE = "Content-Type"
|
||||
_CONTENT_BINARY = "application/octet-stream"
|
||||
_CONTENT_JSON = "application/json"
|
||||
_CONTENT_HTML = "text/html"
|
||||
_CONTENT_PLAIN = "text/plain"
|
||||
_CONTENT_XHTML = "application/xhtml+xml"
|
||||
_CONTENT_XML = "text/xml"
|
||||
_DEFAULT_CHARSET = "UTF-8"
|
||||
)
|
||||
|
||||
var (
|
||||
// Provides a temporary buffer to execute templates into and catch errors.
|
||||
bufpool = sync.Pool{
|
||||
New: func() interface{} { return new(bytes.Buffer) },
|
||||
// Renderer is a Middleware that injects a template renderer into the macaron context, enabling ctx.HTML calls in the handlers.
|
||||
// If MACARON_ENV is set to "development" then templates will be recompiled on every request. For more performance, set the
|
||||
// MACARON_ENV environment variable to "production".
|
||||
func Renderer(dir, leftDelim, rightDelim string) func(http.Handler) http.Handler {
|
||||
fs := os.DirFS(dir)
|
||||
t, err := compileTemplates(fs, leftDelim, rightDelim)
|
||||
if err != nil {
|
||||
panic("Renderer: " + err.Error())
|
||||
}
|
||||
|
||||
// Included helper functions for use when rendering html
|
||||
helperFuncs = template.FuncMap{
|
||||
"yield": func() (string, error) {
|
||||
return "", fmt.Errorf("yield called with no layout defined")
|
||||
},
|
||||
"current": func() (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := FromContext(req.Context())
|
||||
if Env == DEV {
|
||||
if t, err = compileTemplates(fs, leftDelim, rightDelim); err != nil {
|
||||
panic("Context.HTML:" + err.Error())
|
||||
}
|
||||
}
|
||||
ctx.template = t
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// TemplateFile represents a interface of template file that has name and can be read.
|
||||
TemplateFile interface {
|
||||
Name() string
|
||||
Data() []byte
|
||||
Ext() string
|
||||
}
|
||||
// TemplateFileSystem represents a interface of template file system that able to list all files.
|
||||
TemplateFileSystem interface {
|
||||
ListFiles() []TemplateFile
|
||||
Get(string) (io.Reader, error)
|
||||
}
|
||||
|
||||
// Delims represents a set of Left and Right delimiters for HTML template rendering
|
||||
Delims struct {
|
||||
// Left delimiter, defaults to {{
|
||||
Left string
|
||||
// Right delimiter, defaults to }}
|
||||
Right string
|
||||
}
|
||||
|
||||
// RenderOptions represents a struct for specifying configuration options for the Render middleware.
|
||||
RenderOptions struct {
|
||||
// Directory to load templates. Default is "templates".
|
||||
Directory string
|
||||
// Addtional directories to overwite templates.
|
||||
AppendDirectories []string
|
||||
// Layout template name. Will not render a layout if "". Default is to "".
|
||||
Layout string
|
||||
// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
|
||||
Extensions []string
|
||||
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
|
||||
Funcs []template.FuncMap
|
||||
// Delims sets the action delimiters to the specified strings in the Delims struct.
|
||||
Delims Delims
|
||||
// Appends the given charset to the Content-Type header. Default is "UTF-8".
|
||||
Charset string
|
||||
// Outputs human readable JSON.
|
||||
IndentJSON bool
|
||||
// Outputs human readable XML.
|
||||
IndentXML bool
|
||||
// Prefixes the JSON output with the given bytes.
|
||||
PrefixJSON []byte
|
||||
// Prefixes the XML output with the given bytes.
|
||||
PrefixXML []byte
|
||||
// Allows changing of output to XHTML instead of HTML. Default is "text/html"
|
||||
HTMLContentType string
|
||||
// TemplateFileSystem is the interface for supporting any implmentation of template file system.
|
||||
TemplateFileSystem
|
||||
}
|
||||
|
||||
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
|
||||
HTMLOptions struct {
|
||||
// Layout template name. Overrides Options.Layout.
|
||||
Layout string
|
||||
}
|
||||
|
||||
Render interface {
|
||||
http.ResponseWriter
|
||||
SetResponseWriter(http.ResponseWriter)
|
||||
|
||||
JSON(int, interface{})
|
||||
JSONString(interface{}) (string, error)
|
||||
RawData(int, []byte) // Serve content as binary
|
||||
PlainText(int, []byte) // Serve content as plain text
|
||||
HTML(int, string, interface{}, ...HTMLOptions)
|
||||
HTMLSet(int, string, string, interface{}, ...HTMLOptions)
|
||||
HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
|
||||
HTMLString(string, interface{}, ...HTMLOptions) (string, error)
|
||||
HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
|
||||
HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
|
||||
XML(int, interface{})
|
||||
Error(int, ...string)
|
||||
Status(int)
|
||||
SetTemplatePath(string, string)
|
||||
HasTemplateSet(string) bool
|
||||
}
|
||||
)
|
||||
|
||||
// TplFile implements TemplateFile interface.
|
||||
type TplFile struct {
|
||||
name string
|
||||
data []byte
|
||||
ext string
|
||||
}
|
||||
|
||||
// NewTplFile cerates new template file with given name and data.
|
||||
func NewTplFile(name string, data []byte, ext string) *TplFile {
|
||||
return &TplFile{name, data, ext}
|
||||
}
|
||||
|
||||
func (f *TplFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *TplFile) Data() []byte {
|
||||
return f.data
|
||||
}
|
||||
|
||||
func (f *TplFile) Ext() string {
|
||||
return f.ext
|
||||
}
|
||||
|
||||
// TplFileSystem implements TemplateFileSystem interface.
|
||||
type TplFileSystem struct {
|
||||
files []TemplateFile
|
||||
}
|
||||
|
||||
// NewTemplateFileSystem creates new template file system with given options.
|
||||
func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
|
||||
fs := TplFileSystem{}
|
||||
fs.files = make([]TemplateFile, 0, 10)
|
||||
|
||||
// Directories are composed in reverse order because later one overwrites previous ones,
|
||||
// so once found, we can directly jump out of the loop.
|
||||
dirs := make([]string, 0, len(opt.AppendDirectories)+1)
|
||||
for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {
|
||||
dirs = append(dirs, opt.AppendDirectories[i])
|
||||
}
|
||||
dirs = append(dirs, opt.Directory)
|
||||
|
||||
var err error
|
||||
for i := range dirs {
|
||||
// Skip ones that does not exists for symlink test,
|
||||
// but allow non-symlink ones added after start.
|
||||
if _, err := os.Stat(dirs[i]); err != nil && os.IsNotExist(err) {
|
||||
continue
|
||||
func compileTemplates(filesystem fs.FS, leftDelim, rightDelim string) (*template.Template, error) {
|
||||
t := template.New("")
|
||||
t.Delims(leftDelim, rightDelim)
|
||||
err := fs.WalkDir(filesystem, ".", func(path string, d fs.DirEntry, e error) error {
|
||||
if e != nil {
|
||||
return nil // skip unreadable or erroneous filesystem items
|
||||
}
|
||||
|
||||
dirs[i], err = filepath.EvalSymlinks(dirs[i])
|
||||
if err != nil {
|
||||
panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
lastDir := dirs[len(dirs)-1]
|
||||
|
||||
// We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
|
||||
if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, _ error) error {
|
||||
r, err := filepath.Rel(lastDir, path)
|
||||
ext := filepath.Ext(path)
|
||||
if ext != ".html" && ext != ".tmpl" {
|
||||
return nil
|
||||
}
|
||||
data, err := fs.ReadFile(filesystem, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := GetExt(r)
|
||||
|
||||
for _, extension := range opt.Extensions {
|
||||
if ext != extension {
|
||||
continue
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if !omitData {
|
||||
// Loop over candidates of directory, break out once found.
|
||||
// The file always exists because it's inside the walk function,
|
||||
// and read original file is the worst case.
|
||||
for i := range dirs {
|
||||
path = filepath.Join(dirs[i], r)
|
||||
if f, err := os.Stat(path); err != nil || f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
|
||||
fs.files = append(fs.files, NewTplFile(name, data, ext))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic("NewTemplateFileSystem: " + err.Error())
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
func (fs TplFileSystem) ListFiles() []TemplateFile {
|
||||
return fs.files
|
||||
}
|
||||
|
||||
func (fs TplFileSystem) Get(name string) (io.Reader, error) {
|
||||
for i := range fs.files {
|
||||
if fs.files[i].Name()+fs.files[i].Ext() == name {
|
||||
return bytes.NewReader(fs.files[i].Data()), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("file '%s' not found", name)
|
||||
}
|
||||
|
||||
func PrepareCharset(charset string) string {
|
||||
if len(charset) != 0 {
|
||||
return "; charset=" + charset
|
||||
}
|
||||
|
||||
return "; charset=" + _DEFAULT_CHARSET
|
||||
}
|
||||
|
||||
func GetExt(s string) string {
|
||||
index := strings.Index(s, ".")
|
||||
if index == -1 {
|
||||
return ""
|
||||
}
|
||||
return s[index:]
|
||||
}
|
||||
|
||||
func compile(opt RenderOptions) *template.Template {
|
||||
t := template.New(opt.Directory)
|
||||
t.Delims(opt.Delims.Left, opt.Delims.Right)
|
||||
// Parse an initial template in case we don't have any.
|
||||
template.Must(t.Parse("Macaron"))
|
||||
|
||||
if opt.TemplateFileSystem == nil {
|
||||
opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
|
||||
}
|
||||
|
||||
for _, f := range opt.TemplateFileSystem.ListFiles() {
|
||||
tmpl := t.New(f.Name())
|
||||
for _, funcs := range opt.Funcs {
|
||||
tmpl.Funcs(funcs)
|
||||
}
|
||||
// Bomb out if parse fails. We don't want any silent server starts.
|
||||
template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
DEFAULT_TPL_SET_NAME = "DEFAULT"
|
||||
)
|
||||
|
||||
// TemplateSet represents a template set of type *template.Template.
|
||||
type TemplateSet struct {
|
||||
lock sync.RWMutex
|
||||
sets map[string]*template.Template
|
||||
dirs map[string]string
|
||||
}
|
||||
|
||||
// NewTemplateSet initializes a new empty template set.
|
||||
func NewTemplateSet() *TemplateSet {
|
||||
return &TemplateSet{
|
||||
sets: make(map[string]*template.Template),
|
||||
dirs: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {
|
||||
t := compile(*opt)
|
||||
|
||||
ts.lock.Lock()
|
||||
defer ts.lock.Unlock()
|
||||
|
||||
ts.sets[name] = t
|
||||
ts.dirs[name] = opt.Directory
|
||||
return t
|
||||
}
|
||||
|
||||
func (ts *TemplateSet) Get(name string) *template.Template {
|
||||
ts.lock.RLock()
|
||||
defer ts.lock.RUnlock()
|
||||
|
||||
return ts.sets[name]
|
||||
}
|
||||
|
||||
func (ts *TemplateSet) GetDir(name string) string {
|
||||
ts.lock.RLock()
|
||||
defer ts.lock.RUnlock()
|
||||
|
||||
return ts.dirs[name]
|
||||
}
|
||||
|
||||
func prepareRenderOptions(options []RenderOptions) RenderOptions {
|
||||
var opt RenderOptions
|
||||
if len(options) > 0 {
|
||||
opt = options[0]
|
||||
}
|
||||
|
||||
// Defaults.
|
||||
if len(opt.Directory) == 0 {
|
||||
opt.Directory = "templates"
|
||||
}
|
||||
if len(opt.Extensions) == 0 {
|
||||
opt.Extensions = []string{".tmpl", ".html"}
|
||||
}
|
||||
if len(opt.HTMLContentType) == 0 {
|
||||
opt.HTMLContentType = _CONTENT_HTML
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
func ParseTplSet(tplSet string) (tplName string, tplDir string) {
|
||||
tplSet = strings.TrimSpace(tplSet)
|
||||
if len(tplSet) == 0 {
|
||||
panic("empty template set argument")
|
||||
}
|
||||
infos := strings.Split(tplSet, ":")
|
||||
if len(infos) == 1 {
|
||||
tplDir = infos[0]
|
||||
tplName = path.Base(tplDir)
|
||||
} else {
|
||||
tplName = infos[0]
|
||||
tplDir = infos[1]
|
||||
}
|
||||
|
||||
dir, err := os.Stat(tplDir)
|
||||
if err != nil || !dir.IsDir() {
|
||||
panic("template set path does not exist or is not a directory")
|
||||
}
|
||||
return tplName, tplDir
|
||||
}
|
||||
|
||||
func renderHandler(opt RenderOptions, tplSets []string) Handler {
|
||||
cs := PrepareCharset(opt.Charset)
|
||||
ts := NewTemplateSet()
|
||||
ts.Set(DEFAULT_TPL_SET_NAME, &opt)
|
||||
|
||||
var tmpOpt RenderOptions
|
||||
for _, tplSet := range tplSets {
|
||||
tplName, tplDir := ParseTplSet(tplSet)
|
||||
tmpOpt = opt
|
||||
tmpOpt.Directory = tplDir
|
||||
ts.Set(tplName, &tmpOpt)
|
||||
}
|
||||
|
||||
return func(ctx *Context) {
|
||||
r := &TplRender{
|
||||
ResponseWriter: ctx.Resp,
|
||||
TemplateSet: ts,
|
||||
Opt: &opt,
|
||||
CompiledCharset: cs,
|
||||
}
|
||||
ctx.Data["TmplLoadTimes"] = func() string {
|
||||
if r.startTime.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
|
||||
}
|
||||
|
||||
ctx.Render = r
|
||||
ctx.MapTo(r, (*Render)(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
|
||||
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
|
||||
// HTML rendering. The default directory for templates is "templates" and the default
|
||||
// file extension is ".tmpl" and ".html".
|
||||
//
|
||||
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
|
||||
// MACARON_ENV environment variable to "production".
|
||||
func Renderer(options ...RenderOptions) Handler {
|
||||
return renderHandler(prepareRenderOptions(options), []string{})
|
||||
}
|
||||
|
||||
func Renderers(options RenderOptions, tplSets ...string) Handler {
|
||||
return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
|
||||
}
|
||||
|
||||
type TplRender struct {
|
||||
http.ResponseWriter
|
||||
*TemplateSet
|
||||
Opt *RenderOptions
|
||||
CompiledCharset string
|
||||
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
|
||||
r.ResponseWriter = rw
|
||||
}
|
||||
|
||||
func (r *TplRender) JSON(status int, v interface{}) {
|
||||
var (
|
||||
result []byte
|
||||
err error
|
||||
)
|
||||
if r.Opt.IndentJSON {
|
||||
result, err = json.MarshalIndent(v, "", " ")
|
||||
} else {
|
||||
result, err = json.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(r, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// json rendered fine, write out the result
|
||||
r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)
|
||||
r.WriteHeader(status)
|
||||
if len(r.Opt.PrefixJSON) > 0 {
|
||||
_, _ = r.Write(r.Opt.PrefixJSON)
|
||||
}
|
||||
_, _ = r.Write(result)
|
||||
}
|
||||
|
||||
func (r *TplRender) JSONString(v interface{}) (string, error) {
|
||||
var result []byte
|
||||
var err error
|
||||
if r.Opt.IndentJSON {
|
||||
result, err = json.MarshalIndent(v, "", " ")
|
||||
} else {
|
||||
result, err = json.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func (r *TplRender) XML(status int, v interface{}) {
|
||||
var result []byte
|
||||
var err error
|
||||
if r.Opt.IndentXML {
|
||||
result, err = xml.MarshalIndent(v, "", " ")
|
||||
} else {
|
||||
result, err = xml.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(r, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// XML rendered fine, write out the result
|
||||
r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)
|
||||
r.WriteHeader(status)
|
||||
if len(r.Opt.PrefixXML) > 0 {
|
||||
_, _ = r.Write(r.Opt.PrefixXML)
|
||||
}
|
||||
_, _ = r.Write(result)
|
||||
}
|
||||
|
||||
func (r *TplRender) data(status int, contentType string, v []byte) {
|
||||
if r.Header().Get(_CONTENT_TYPE) == "" {
|
||||
r.Header().Set(_CONTENT_TYPE, contentType)
|
||||
}
|
||||
r.WriteHeader(status)
|
||||
_, _ = r.Write(v)
|
||||
}
|
||||
|
||||
func (r *TplRender) RawData(status int, v []byte) {
|
||||
r.data(status, _CONTENT_BINARY, v)
|
||||
}
|
||||
|
||||
func (r *TplRender) PlainText(status int, v []byte) {
|
||||
r.data(status, _CONTENT_PLAIN, v)
|
||||
}
|
||||
|
||||
func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
|
||||
buf := bufpool.Get().(*bytes.Buffer)
|
||||
return buf, t.ExecuteTemplate(buf, name, data)
|
||||
}
|
||||
|
||||
func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
|
||||
funcs := template.FuncMap{
|
||||
"yield": func() (template.HTML, error) {
|
||||
buf, err := r.execute(t, tplName, data)
|
||||
// return safe html here since we are rendering our own template
|
||||
return template.HTML(buf.String()), err
|
||||
},
|
||||
"current": func() (string, error) {
|
||||
return tplName, nil
|
||||
},
|
||||
}
|
||||
t.Funcs(funcs)
|
||||
}
|
||||
|
||||
func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
|
||||
t := r.TemplateSet.Get(setName)
|
||||
if Env == DEV {
|
||||
opt := *r.Opt
|
||||
opt.Directory = r.TemplateSet.GetDir(setName)
|
||||
t = r.TemplateSet.Set(setName, &opt)
|
||||
}
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
|
||||
}
|
||||
|
||||
opt := r.prepareHTMLOptions(htmlOpt)
|
||||
|
||||
if len(opt.Layout) > 0 {
|
||||
r.addYield(t, tplName, data)
|
||||
tplName = opt.Layout
|
||||
}
|
||||
|
||||
out, err := r.execute(t, tplName, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
|
||||
r.startTime = time.Now()
|
||||
|
||||
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
|
||||
if err != nil {
|
||||
http.Error(r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)
|
||||
r.WriteHeader(status)
|
||||
|
||||
if _, err := out.WriteTo(r); err != nil {
|
||||
out.Reset()
|
||||
}
|
||||
bufpool.Put(out)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
|
||||
r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
|
||||
r.renderHTML(status, setName, tplName, data, htmlOpt...)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
|
||||
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
|
||||
return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
||||
p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
|
||||
return string(p), err
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
||||
p, err := r.HTMLBytes(name, data, htmlOpt...)
|
||||
return string(p), err
|
||||
}
|
||||
|
||||
// Error writes the given HTTP status to the current ResponseWriter
|
||||
func (r *TplRender) Error(status int, message ...string) {
|
||||
r.WriteHeader(status)
|
||||
if len(message) > 0 {
|
||||
_, _ = r.Write([]byte(message[0]))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TplRender) Status(status int) {
|
||||
r.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
|
||||
if len(htmlOpt) > 0 {
|
||||
return htmlOpt[0]
|
||||
}
|
||||
|
||||
return HTMLOptions{
|
||||
Layout: r.Opt.Layout,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TplRender) SetTemplatePath(setName, dir string) {
|
||||
if len(setName) == 0 {
|
||||
setName = DEFAULT_TPL_SET_NAME
|
||||
}
|
||||
opt := *r.Opt
|
||||
opt.Directory = dir
|
||||
r.TemplateSet.Set(setName, &opt)
|
||||
}
|
||||
|
||||
func (r *TplRender) HasTemplateSet(name string) bool {
|
||||
return r.TemplateSet.Get(name) != nil
|
||||
}
|
||||
|
||||
// DummyRender is used when user does not choose any real render to use.
|
||||
// This way, we can print out friendly message which asks them to register one,
|
||||
// instead of ugly and confusing 'nil pointer' panic.
|
||||
type DummyRender struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func renderNotRegistered() {
|
||||
panic("middleware render hasn't been registered")
|
||||
}
|
||||
|
||||
func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) JSON(int, interface{}) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) JSONString(interface{}) (string, error) {
|
||||
renderNotRegistered()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (r *DummyRender) RawData(int, []byte) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) PlainText(int, []byte) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {
|
||||
renderNotRegistered()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {
|
||||
renderNotRegistered()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {
|
||||
renderNotRegistered()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {
|
||||
renderNotRegistered()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *DummyRender) XML(int, interface{}) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) Error(int, ...string) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) Status(int) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) SetTemplatePath(string, string) {
|
||||
renderNotRegistered()
|
||||
}
|
||||
|
||||
func (r *DummyRender) HasTemplateSet(string) bool {
|
||||
renderNotRegistered()
|
||||
return false
|
||||
basename := path[:len(path)-len(ext)]
|
||||
_, err = t.New(basename).Parse(string(data))
|
||||
return err
|
||||
})
|
||||
return t, err
|
||||
}
|
||||
|
||||
+30
-31
@@ -10,45 +10,44 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// AddCSPHeader adds the Content Security Policy header.
|
||||
func AddCSPHeader(cfg *setting.Cfg, logger log.Logger) macaron.Handler {
|
||||
return func(w http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
if !cfg.CSPEnabled {
|
||||
return
|
||||
}
|
||||
func AddCSPHeader(cfg *setting.Cfg, logger log.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if !cfg.CSPEnabled {
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("Adding CSP header to response", "cfg", fmt.Sprintf("%p", cfg))
|
||||
logger.Debug("Adding CSP header to response", "cfg", fmt.Sprintf("%p", cfg))
|
||||
|
||||
ctx, ok := c.Data["ctx"].(*models.ReqContext)
|
||||
if !ok {
|
||||
panic("Failed to convert context into models.ReqContext")
|
||||
}
|
||||
ctx := contexthandler.FromContext(req.Context())
|
||||
if cfg.CSPTemplate == "" {
|
||||
logger.Debug("CSP template not configured, so returning 500")
|
||||
ctx.JsonApiErr(500, "CSP template has to be configured", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.CSPTemplate == "" {
|
||||
logger.Debug("CSP template not configured, so returning 500")
|
||||
ctx.JsonApiErr(500, "CSP template has to be configured", nil)
|
||||
return
|
||||
}
|
||||
var buf [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
|
||||
logger.Error("Failed to generate CSP nonce", "err", err)
|
||||
ctx.JsonApiErr(500, "Failed to generate CSP nonce", err)
|
||||
}
|
||||
|
||||
var buf [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
|
||||
logger.Error("Failed to generate CSP nonce", "err", err)
|
||||
ctx.JsonApiErr(500, "Failed to generate CSP nonce", err)
|
||||
}
|
||||
nonce := base64.RawStdEncoding.EncodeToString(buf[:])
|
||||
val := strings.ReplaceAll(cfg.CSPTemplate, "$NONCE", fmt.Sprintf("'nonce-%s'", nonce))
|
||||
|
||||
nonce := base64.RawStdEncoding.EncodeToString(buf[:])
|
||||
val := strings.ReplaceAll(cfg.CSPTemplate, "$NONCE", fmt.Sprintf("'nonce-%s'", nonce))
|
||||
|
||||
re := regexp.MustCompile(`^\w+:(//)?`)
|
||||
rootPath := re.ReplaceAllString(cfg.AppURL, "")
|
||||
val = strings.ReplaceAll(val, "$ROOT_PATH", rootPath)
|
||||
w.Header().Set("Content-Security-Policy", val)
|
||||
ctx.RequestNonce = nonce
|
||||
logger.Debug("Successfully generated CSP nonce", "nonce", nonce)
|
||||
re := regexp.MustCompile(`^\w+:(//)?`)
|
||||
rootPath := re.ReplaceAllString(cfg.AppURL, "")
|
||||
val = strings.ReplaceAll(val, "$ROOT_PATH", rootPath)
|
||||
rw.Header().Set("Content-Security-Policy", val)
|
||||
ctx.RequestNonce = nonce
|
||||
logger.Debug("Successfully generated CSP nonce", "nonce", nonce)
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+64
-26
@@ -1,43 +1,81 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-macaron/gzip"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"gopkg.in/macaron.v1"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
const resourcesPath = "/resources"
|
||||
|
||||
var gzipIgnoredPathPrefixes = []string{
|
||||
"/api/datasources/proxy", // Ignore datasource proxy requests.
|
||||
"/api/plugin-proxy/",
|
||||
"/metrics",
|
||||
"/api/live/ws", // WebSocket does not support gzip compression.
|
||||
"/api/live/push", // WebSocket does not support gzip compression.
|
||||
type gzipResponseWriter struct {
|
||||
w *gzip.Writer
|
||||
macaron.ResponseWriter
|
||||
}
|
||||
|
||||
func Gziper() macaron.Handler {
|
||||
gziperLogger := log.New("gziper")
|
||||
gziper := gzip.Gziper()
|
||||
func (grw *gzipResponseWriter) WriteHeader(c int) {
|
||||
grw.Header().Del("Content-Length")
|
||||
grw.ResponseWriter.WriteHeader(c)
|
||||
}
|
||||
|
||||
return func(ctx *macaron.Context) {
|
||||
requestPath := ctx.Req.URL.RequestURI()
|
||||
func (grw gzipResponseWriter) Write(p []byte) (int, error) {
|
||||
if grw.Header().Get("Content-Type") == "" {
|
||||
grw.Header().Set("Content-Type", http.DetectContentType(p))
|
||||
}
|
||||
grw.Header().Del("Content-Length")
|
||||
return grw.w.Write(p)
|
||||
}
|
||||
|
||||
for _, pathPrefix := range gzipIgnoredPathPrefixes {
|
||||
if strings.HasPrefix(requestPath, pathPrefix) {
|
||||
func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := grw.ResponseWriter.(http.Hijacker); ok {
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("GZIP ResponseWriter doesn't implement the Hijacker interface")
|
||||
}
|
||||
|
||||
type matcher func(s string) bool
|
||||
|
||||
func prefix(p string) matcher { return func(s string) bool { return strings.HasPrefix(s, p) } }
|
||||
func substr(p string) matcher { return func(s string) bool { return strings.Contains(s, p) } }
|
||||
|
||||
var gzipIgnoredPaths = []matcher{
|
||||
prefix("/api/datasources"),
|
||||
prefix("/api/plugins"),
|
||||
prefix("/api/plugin-proxy/"),
|
||||
prefix("/metrics"),
|
||||
prefix("/api/live/ws"), // WebSocket does not support gzip compression.
|
||||
prefix("/api/live/push"), // WebSocket does not support gzip compression.
|
||||
substr("/resources"),
|
||||
}
|
||||
|
||||
func Gziper() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
requestPath := req.URL.RequestURI()
|
||||
|
||||
for _, pathMatcher := range gzipIgnoredPaths {
|
||||
if pathMatcher(requestPath) {
|
||||
fmt.Println("skip path", requestPath)
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ignore resources
|
||||
if (strings.HasPrefix(requestPath, "/api/datasources/") || strings.HasPrefix(requestPath, "/api/plugins/")) && strings.Contains(requestPath, resourcesPath) {
|
||||
return
|
||||
}
|
||||
grw := &gzipResponseWriter{gzip.NewWriter(rw), rw.(macaron.ResponseWriter)}
|
||||
grw.Header().Set("Content-Encoding", "gzip")
|
||||
grw.Header().Set("Vary", "Accept-Encoding")
|
||||
|
||||
if _, err := ctx.Invoke(gziper); err != nil {
|
||||
gziperLogger.Error("Invoking gzip handler failed", "err", err)
|
||||
}
|
||||
next.ServeHTTP(grw, req)
|
||||
// We can't really handle close errors at this point and we can't report them to the caller
|
||||
_ = grw.w.Close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
Settings: map[string]interface{}{},
|
||||
NavTree: []*dtos.NavLink{},
|
||||
}
|
||||
t.Log("Calling HTML", "data", data, "render", c.Render)
|
||||
t.Log("Calling HTML", "data", data)
|
||||
c.HTML(200, "index-template", data)
|
||||
t.Log("Returned HTML with code 200")
|
||||
}
|
||||
@@ -632,11 +632,8 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
|
||||
|
||||
sc.m = macaron.New()
|
||||
sc.m.Use(AddDefaultResponseHeaders(cfg))
|
||||
sc.m.Use(AddCSPHeader(cfg, logger))
|
||||
sc.m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: viewsPath,
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
sc.m.UseMiddleware(AddCSPHeader(cfg, logger))
|
||||
sc.m.UseMiddleware(macaron.Renderer(viewsPath, "[[", "]]"))
|
||||
|
||||
ctxHdlr := getContextHandler(t, cfg)
|
||||
sc.sqlStore = ctxHdlr.SQLStore
|
||||
|
||||
@@ -37,7 +37,7 @@ func OrgRedirect(cfg *setting.Cfg) macaron.Handler {
|
||||
if ctx.IsApiRequest() {
|
||||
ctx.JsonApiErr(404, "Not found", nil)
|
||||
} else {
|
||||
ctx.Error(404, "Not found")
|
||||
http.Error(ctx.Resp, "Not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -32,10 +32,7 @@ func rateLimiterScenario(t *testing.T, desc string, rps int, burst int, fn rateL
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
m := macaron.New()
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: "",
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
m.UseMiddleware(macaron.Renderer("../../public/views", "[[", "]]"))
|
||||
m.Use(getContextHandler(t, cfg).Middleware)
|
||||
m.Get("/foo", RateLimit(rps, burst, func() time.Time { return currentTime }), defaultHandler)
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ func Recovery(cfg *setting.Cfg) macaron.Handler {
|
||||
|
||||
c.JSON(500, resp)
|
||||
} else {
|
||||
c.HTML(500, cfg.ErrTemplateName)
|
||||
c.HTML(500, cfg.ErrTemplateName, c.Data)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -65,10 +65,7 @@ func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
|
||||
sc.m.Use(Recovery(cfg))
|
||||
|
||||
sc.m.Use(AddDefaultResponseHeaders(cfg))
|
||||
sc.m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: viewsPath,
|
||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||
}))
|
||||
sc.m.UseMiddleware(macaron.Renderer(viewsPath, "[[", "]]"))
|
||||
|
||||
sc.userAuthTokenService = auth.NewFakeUserAuthTokenService()
|
||||
sc.remoteCacheService = remotecache.NewFakeStore(t)
|
||||
|
||||
@@ -36,7 +36,7 @@ func (ctx *ReqContext) Handle(cfg *setting.Cfg, status int, title string, err er
|
||||
ctx.Data["AppSubUrl"] = cfg.AppSubURL
|
||||
ctx.Data["Theme"] = "dark"
|
||||
|
||||
ctx.HTML(status, cfg.ErrTemplateName)
|
||||
ctx.HTML(status, cfg.ErrTemplateName, ctx.Data)
|
||||
}
|
||||
|
||||
func (ctx *ReqContext) IsApiRequest() bool {
|
||||
|
||||
@@ -475,7 +475,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
||||
n, err = channels.NewDiscordNotifier(cfg, tmpl)
|
||||
case "googlechat":
|
||||
n, err = channels.NewGoogleChatNotifier(cfg, tmpl)
|
||||
case "line":
|
||||
case "LINE":
|
||||
n, err = channels.NewLineNotifier(cfg, tmpl)
|
||||
case "threema":
|
||||
n, err = channels.NewThreemaNotifier(cfg, tmpl)
|
||||
|
||||
@@ -723,7 +723,7 @@ const alertmanagerConfig = `
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "line_test",
|
||||
"type": "line",
|
||||
"type": "LINE",
|
||||
"settings": {},
|
||||
"secureSettings": {
|
||||
"token": "mysecrettoken"
|
||||
@@ -1217,7 +1217,7 @@ var expAlertmanagerConfigFromAPI = `
|
||||
{
|
||||
"uid": "",
|
||||
"name": "line_test",
|
||||
"type": "line",
|
||||
"type": "LINE",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {},
|
||||
"secureFields": {
|
||||
|
||||
+17
-17
@@ -3,23 +3,23 @@
|
||||
Frame[0]
|
||||
Name:
|
||||
Dimensions: 7 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+
|
||||
| Name: Time | Name: foo | Name: foo | Name: foo | Name: bar | Name: bar | Name: bar |
|
||||
| Labels: | Labels: state=CA | Labels: state=OR | Labels: state=NV | Labels: state=CA | Labels: state=OR | Labels: state=NV |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+-------------------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109375384 | 0.9882682601465907 | 0 | 0.6508732545187114 | -0.1527280131044415 | 1 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643906261 | 0.7446917214252602 | 0.5458434494486994 | 0.13096142733310906 | -0.6674086005144695 | 0.8378871813639024 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854179972 | 0.25966703475349645 | 0.9147104586490411 | -0.4314114520876468 | -0.9656982091017495 | 0.40410985738788996 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | 0.520421234112239 | -0.309548361739801 | 0.9870048864743552 | -0.8539096785287623 | -0.9508837004303972 | -0.16069024262768705 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.03004672347354234 | -0.7784002433814474 | 0.7392870259917509 | -0.9995484952759943 | -0.6277683180152559 | -0.6733904463238785 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629931729 | -0.994874810059912 | 0.2518733583799049 | -0.8211080641579032 | -0.10111435263232425 | -0.9677602034277024 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396937303 | -0.8887854574406474 | -0.31720410936455373 | -0.37644334766887727 | 0.4583234781701883 | -0.9483572918485101 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.981731033820365 | -0.4945290734844293 | -0.7834358726049309 | 0.19027395311496903 | 0.8691610871861625 | -0.6214726329580013 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578767007 | 0.060066314471906294 | -0.9956576407880757 | 0.6952995601938146 | 0.998194388817009 | -0.09309061359945539 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | -0.22268161314765392 | 0.5951866633399958 | -0.8850616758817763 | 0.9748912242737435 | 0.8035874786121313 | 0.46547376927743367 |
|
||||
+-------------------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+
|
||||
+-------------------------------+------------------+------------------+------------------+------------------+------------------+------------------+
|
||||
| Name: Time | Name: foo | Name: foo | Name: foo | Name: bar | Name: bar | Name: bar |
|
||||
| Labels: | Labels: state=CA | Labels: state=OR | Labels: state=NV | Labels: state=CA | Labels: state=OR | Labels: state=NV |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+-------------------------------+------------------+------------------+------------------+------------------+------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109 | 0.9882682601 | 0 | 0.6508732545 | -0.1527280131 | 1 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643 | 0.7446917214 | 0.5458434494 | 0.1309614273 | -0.6674086005 | 0.8378871813 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854 | 0.2596670347 | 0.9147104586 | -0.431411452 | -0.9656982091 | 0.4041098573 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | 0.5204212341 | -0.3095483617 | 0.9870048864 | -0.8539096785 | -0.9508837004 | -0.1606902426 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.0300467234 | -0.7784002433 | 0.7392870259 | -0.9995484952 | -0.627768318 | -0.6733904463 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629 | -0.99487481 | 0.2518733583 | -0.8211080641 | -0.1011143526 | -0.9677602034 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396 | -0.8887854574 | -0.3172041093 | -0.3764433476 | 0.4583234781 | -0.9483572918 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.9817310338 | -0.4945290734 | -0.7834358726 | 0.1902739531 | 0.8691610871 | -0.6214726329 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578 | 0.0600663144 | -0.9956576407 | 0.6952995601 | 0.9981943888 | -0.0930906135 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | -0.2226816131 | 0.5951866633 | -0.8850616758 | 0.9748912242 | 0.8035874786 | 0.4654737692 |
|
||||
+-------------------------------+------------------+------------------+------------------+------------------+------------------+------------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=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
|
||||
FRAME=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
|
||||
|
||||
+102
-102
@@ -3,138 +3,138 @@
|
||||
Frame[0]
|
||||
Name: foo
|
||||
Dimensions: 2 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=CA |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109375384 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643906261 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854179972 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | 0.520421234112239 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.03004672347354234 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629931729 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396937303 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.981731033820365 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578767007 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | -0.22268161314765392 |
|
||||
+-------------------------------+----------------------+
|
||||
+-------------------------------+------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=CA |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.7591864109 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.9913874643 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.9021552854 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | 0.5204212341 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.0300467234 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.5707727629 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.9264396396 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.9817310338 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.7187200578 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | -0.2226816131 |
|
||||
+-------------------------------+------------------+
|
||||
|
||||
|
||||
|
||||
Frame[1]
|
||||
Name: foo
|
||||
Dimensions: 2 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=OR |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.9882682601465907 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.7446917214252602 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.25966703475349645 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.309548361739801 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.7784002433814474 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.994874810059912 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.8887854574406474 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.4945290734844293 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | 0.060066314471906294 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.5951866633399958 |
|
||||
+-------------------------------+----------------------+
|
||||
+-------------------------------+------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=OR |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.9882682601 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.7446917214 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.2596670347 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.3095483617 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.7784002433 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.99487481 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.8887854574 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.4945290734 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | 0.0600663144 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.5951866633 |
|
||||
+-------------------------------+------------------+
|
||||
|
||||
|
||||
|
||||
Frame[2]
|
||||
Name: foo
|
||||
Dimensions: 2 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=NV |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.5458434494486994 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.9147104586490411 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | 0.9870048864743552 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | 0.7392870259917509 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | 0.2518733583799049 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.31720410936455373 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.7834358726049309 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.9956576407880757 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | -0.8850616758817763 |
|
||||
+-------------------------------+----------------------+
|
||||
+-------------------------------+------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=NV |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.5458434494 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.9147104586 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | 0.9870048864 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | 0.7392870259 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | 0.2518733583 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.3172041093 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.7834358726 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.9956576407 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | -0.8850616758 |
|
||||
+-------------------------------+------------------+
|
||||
|
||||
|
||||
|
||||
Frame[3]
|
||||
Name: bar
|
||||
Dimensions: 2 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=CA |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.6508732545187114 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.13096142733310906 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | -0.4314114520876468 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.8539096785287623 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.9995484952759943 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.8211080641579032 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.37644334766887727 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | 0.19027395311496903 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | 0.6952995601938146 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.9748912242737435 |
|
||||
+-------------------------------+----------------------+
|
||||
+-------------------------------+------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=CA |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 0.6508732545 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.1309614273 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | -0.431411452 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.8539096785 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.9995484952 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.8211080641 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.3764433476 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | 0.1902739531 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | 0.6952995601 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.9748912242 |
|
||||
+-------------------------------+------------------+
|
||||
|
||||
|
||||
|
||||
Frame[4]
|
||||
Name: bar
|
||||
Dimensions: 2 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=OR |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | -0.1527280131044415 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | -0.6674086005144695 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | -0.9656982091017495 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.9508837004303972 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.6277683180152559 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.10111435263232425 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | 0.4583234781701883 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | 0.8691610871861625 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | 0.998194388817009 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.8035874786121313 |
|
||||
+-------------------------------+----------------------+
|
||||
+-------------------------------+------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=OR |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | -0.1527280131 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | -0.6674086005 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | -0.9656982091 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.9508837004 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.627768318 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.1011143526 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | 0.4583234781 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | 0.8691610871 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | 0.9981943888 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.8035874786 |
|
||||
+-------------------------------+------------------+
|
||||
|
||||
|
||||
|
||||
Frame[5]
|
||||
Name: bar
|
||||
Dimensions: 2 Fields by 10 Rows
|
||||
+-------------------------------+----------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=NV |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+----------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 1 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.8378871813639024 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.40410985738788996 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.16069024262768705 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.6733904463238785 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.9677602034277024 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.9483572918485101 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.6214726329580013 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.09309061359945539 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.46547376927743367 |
|
||||
+-------------------------------+----------------------+
|
||||
+-------------------------------+------------------+
|
||||
| Name: Time | Name: Value |
|
||||
| Labels: | Labels: state=NV |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+-------------------------------+------------------+
|
||||
| 2020-01-10 23:00:00 +0000 UTC | 1 |
|
||||
| 2020-01-10 23:00:01 +0000 UTC | 0.8378871813 |
|
||||
| 2020-01-10 23:00:02 +0000 UTC | 0.4041098573 |
|
||||
| 2020-01-10 23:00:03 +0000 UTC | -0.1606902426 |
|
||||
| 2020-01-10 23:00:04 +0000 UTC | -0.6733904463 |
|
||||
| 2020-01-10 23:00:05 +0000 UTC | -0.9677602034 |
|
||||
| 2020-01-10 23:00:06 +0000 UTC | -0.9483572918 |
|
||||
| 2020-01-10 23:00:07 +0000 UTC | -0.6214726329 |
|
||||
| 2020-01-10 23:00:08 +0000 UTC | -0.0930906135 |
|
||||
| 2020-01-10 23:00:09 +0000 UTC | 0.4654737692 |
|
||||
+-------------------------------+------------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVRGvRTEFL6D95Gyc0crnvPz8V2cJ03uw/Kv6Ubkqn4D9o6656kcSev4Qa0D3FQ+K/qXtGvmSl7b9vQXczV2rvv7Xo8TTB/+a/meHxwtSAzL8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVRSQgwuSf7z8u86O7g9TnP2Flh3tintA/DfuM7qPP0788spCgp+jovwFKmrID1u+/f8YbM+5w7L8CW2JFXabfv4cFfxADwa4/zPDB5sQL4z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAAAACaxoOujHfhPwqeJt5ORe0/GsmMRYuV7z/Hu99DPajnP5xsPm+xHtA/CSr4dhJN1L+sPmsb6BHpv0pQpmlt3O+/pV0b3WxS7L8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVZfe/JfTT5D+ScrcTWMPAPyuGdcc+nNu/5JrgYzpT67+4Pj8gTfzvvzRIQWuERuq/pzbC1qUX2L8uaPSa5VrIP2qU/tzkP+Y/XDitFE8y7z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVuLq7bpeMw7+66whIaVvlv6W6PO7/5u6/jrx0p6Nt7r/75GqVrRbkv4dItlWh4rm/J7du/ytV3T+xc43pKtDrP+Oh4Fs18e8/jqeDFv226T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAA8D80QjbH+M/qP7Y4Xpfv3Nk/8KRvdH+RxL/+ywwfaozlvwj0Aj/k9+6/BjMtZPFY7r9vPD2TGuPjv2Jn+VTJ1Le/ulAMflLK3T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVgULMTEFL6D/apho0crnvPwec1sJ03uw/i0+Tbkqn4D8Xemt5kcSev0lMwz3FQ+K/0Zk5vmSl7b/odHQzV2rvvwte5zTB/+a/6K7XwtSAzL8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAb0ZwuSf7z9qeqC7g9TnP+eweHtintA/TgqC7qPP07+PgIWgp+jovwoOkrID1u+/WDAWM+5w7L/TJUtFXabfv+Ll4A8Dwa4/knG85sQL4z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAAAAAkFX2ujHfhP4/gH95ORe0/9ZCCRYuV7z+UH9NDPajnP851KG+xHtA/eGvmdhJN1L8vkWob6BHpv2Y1mmlt3O+/ZSAQ3WxS7L8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVDGW9JfTT5D/iPqUTWMPAP5JuXcc+nNu/6KbcYzpT67/ozDQgTfzvv+tSOWuERuq/2Eev1qUX2L96Leya5VrIP5yv8dzkP+Y/vRWjFE8y7z8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVokm5bpeMw7+g7gZIaVvlvxd9PO7/5u6/DY9wp6Nt7r82zGiVrRbkvw++klWh4rm/F2xb/ytV3T8enIHpKtDrP3BL3ls18e8/ufyBFv226T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
FRAME=QVJST1cxAAD/////mAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAoAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAoAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAIAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAABg5zW9qOgVACqCcb2o6BUA9BytvajoFQC+t+i9qOgVAIhSJL6o6BUAUu1fvqjoFQAciJu+qOgVAOYi176o6BUAsL0Sv6joFQB6WE6/qOgVAAAAAAAA8D/WeS3H+M/qPwEQRpfv3Nk/VWxgdH+RxL/Ygwkfaozlv1cl/z7k9+6/OYgmZPFY7r+yQzWTGuPjvy4NjFTJ1Le/0Qf3fVLK3T8QAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAAqAEAAAAAAADAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAD4/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAABj///8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAgAAAKQAAAAEAAAAdv///xQAAABsAAAAbAAAAAAAAANsAAAAAgAAACwAAAAEAAAAaP///wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAjP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAAiv///wAAAgAFAAAAVmFsdWUAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAwAEAAEFSUk9XMQ==
|
||||
|
||||
+16
-16
@@ -3,28 +3,28 @@
|
||||
Frame[0]
|
||||
Name: foo
|
||||
Dimensions: 3 Fields by 1 Rows
|
||||
+--------------------+--------------------+----------------------+
|
||||
| Name: CA | Name: OR | Name: NV |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+--------------------+--------------------+----------------------+
|
||||
| 0.3455559195129913 | 0.9373322369907638 | -0.48750602508751073 |
|
||||
+--------------------+--------------------+----------------------+
|
||||
+-----------------+-----------------+-----------------+
|
||||
| Name: CA | Name: OR | Name: NV |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+-----------------+-----------------+-----------------+
|
||||
| 0.3455559195 | 0.9373322369 | -0.487506025 |
|
||||
+-----------------+-----------------+-----------------+
|
||||
|
||||
|
||||
|
||||
Frame[1]
|
||||
Name: bar
|
||||
Dimensions: 3 Fields by 1 Rows
|
||||
+--------------------+--------------------+-------------------+
|
||||
| Name: CA | Name: OR | Name: NV |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+--------------------+--------------------+-------------------+
|
||||
| 0.9383981598924473 | 0.3484369060502785 | 0.873119622676856 |
|
||||
+--------------------+--------------------+-------------------+
|
||||
+-----------------+-----------------+-----------------+
|
||||
| Name: CA | Name: OR | Name: NV |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+-----------------+-----------------+-----------------+
|
||||
| 0.9383981598 | 0.348436906 | 0.8731196226 |
|
||||
+-----------------+-----------------+-----------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACPXT5OWHdY/S5TrLKD+7T/kpJZ4TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
|
||||
FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACKr65NbB+4/yyQNT8pM1j8akxyQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
|
||||
FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAPRETJOWHdY/0xrfLKD+7T/fln54TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
|
||||
FRAME=QVJST1cxAAD/////sAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAG723pNbB+4/wlL/TspM1j/4AhKQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAADAAQAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAADU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAPT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAAMgAAABcAAAABAAAAFb///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAARP///wgAAAAMAAAAAgAAAE5WAAAEAAAAbmFtZQAAAAAAAAAAOv///wAAAgACAAAATlYAAKr///8UAAAAOAAAADgAAAAAAAADOAAAAAEAAAAEAAAAmP///wgAAAAMAAAAAgAAAE9SAAAEAAAAbmFtZQAAAAAAAAAAjv///wAAAgACAAAAT1IAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABAAAAASAAAAAAAAANIAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAADAAAAAIAAABDQQAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgACAAAAQ0EAAOABAABBUlJPVzE=
|
||||
|
||||
@@ -3,28 +3,28 @@
|
||||
Frame[0]
|
||||
Name: foo
|
||||
Dimensions: 3 Fields by 1 Rows
|
||||
+--------------------+--------------------+----------------------+
|
||||
| Name: | Name: | Name: |
|
||||
| Labels: state=CA | Labels: state=OR | Labels: state=NV |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+--------------------+--------------------+----------------------+
|
||||
| 0.3455559195129913 | 0.9373322369907638 | -0.48750602508751073 |
|
||||
+--------------------+--------------------+----------------------+
|
||||
+------------------+------------------+------------------+
|
||||
| Name: | Name: | Name: |
|
||||
| Labels: state=CA | Labels: state=OR | Labels: state=NV |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+------------------+------------------+------------------+
|
||||
| 0.3455559195 | 0.9373322369 | -0.487506025 |
|
||||
+------------------+------------------+------------------+
|
||||
|
||||
|
||||
|
||||
Frame[1]
|
||||
Name: bar
|
||||
Dimensions: 3 Fields by 1 Rows
|
||||
+--------------------+--------------------+-------------------+
|
||||
| Name: | Name: | Name: |
|
||||
| Labels: state=CA | Labels: state=OR | Labels: state=NV |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+--------------------+--------------------+-------------------+
|
||||
| 0.9383981598924473 | 0.3484369060502785 | 0.873119622676856 |
|
||||
+--------------------+--------------------+-------------------+
|
||||
+------------------+------------------+------------------+
|
||||
| Name: | Name: | Name: |
|
||||
| Labels: state=CA | Labels: state=OR | Labels: state=NV |
|
||||
| Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+------------------+------------------+------------------+
|
||||
| 0.9383981598 | 0.348436906 | 0.8731196226 |
|
||||
+------------------+------------------+------------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACPXT5OWHdY/S5TrLKD+7T/kpJZ4TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGZvbwAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
|
||||
FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAACKr65NbB+4/yyQNT8pM1j8akxyQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGJhcgAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
|
||||
FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABmb28ABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAPRETJOWHdY/0xrfLKD+7T/fln54TDPfvxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGZvbwAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
|
||||
FRAME=QVJST1cxAAD/////QAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAABU/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAHT+//8IAAAADAAAAAMAAABiYXIABAAAAG5hbWUAAAAAAwAAACQBAACMAAAABAAAAPr+//8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAMj+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAA6P7//wgAAAAYAAAADgAAAHsic3RhdGUiOiJOViJ9AAAGAAAAbGFiZWxzAAAAAAAA3v7//wAAAgAAAAAAAAAAAH7///8UAAAAaAAAAGgAAAAAAAADaAAAAAIAAAAoAAAABAAAAEz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAbP///wgAAAAYAAAADgAAAHsic3RhdGUiOiJPUiJ9AAAGAAAAbGFiZWxzAAAAAAAAYv///wAAAgAAAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHAAAAB4AAAAAAAAA3gAAAACAAAAMAAAAAQAAADg////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAYAAAADgAAAHsic3RhdGUiOiJDQSJ9AAAGAAAAbGFiZWxzAAAAAAAAAAAGAAgABgAGAAAAAAACAAAAAAAAAAAAAAAAAP/////oAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAGAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAeAAAAAEAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAACAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAG723pNbB+4/wlL/TspM1j/4AhKQmPDrPxAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABQAgAAAAAAAPAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAFT+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAdP7//wgAAAAMAAAAAwAAAGJhcgAEAAAAbmFtZQAAAAADAAAAJAEAAIwAAAAEAAAA+v7//xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAAyP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAADo/v//CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik5WIn0AAAYAAABsYWJlbHMAAAAAAADe/v//AAACAAAAAAAAAAAAfv///xQAAABoAAAAaAAAAAAAAANoAAAAAgAAACgAAAAEAAAATP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAABs////CAAAABgAAAAOAAAAeyJzdGF0ZSI6Ik9SIn0AAAYAAABsYWJlbHMAAAAAAABi////AAACAAAAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAcAAAAHgAAAAAAAADeAAAAAIAAAAwAAAABAAAAOD///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAACAAMAAgABAAIAAAACAAAABgAAAAOAAAAeyJzdGF0ZSI6IkNBIn0AAAYAAABsYWJlbHMAAAAAAAAAAAYACAAGAAYAAAAAAAIAAAAAAAAAAABoAgAAQVJST1cx
|
||||
|
||||
+17
-17
@@ -3,23 +3,23 @@
|
||||
Frame[0]
|
||||
Name:
|
||||
Dimensions: 4 Fields by 51 Rows
|
||||
+----------------+----------------------+----------------------+---------------------+
|
||||
| Name: state | Name: foo | Name: bar | Name: baz |
|
||||
| Labels: | Labels: | Labels: | Labels: |
|
||||
| Type: []string | Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+----------------+----------------------+----------------------+---------------------+
|
||||
| AL | 0.6779842417778559 | 0.7350764367743711 | 0.9223316213929472 |
|
||||
| AK | 0.9096030926893556 | -0.41547829518515106 | -2.1892914822036755 |
|
||||
| AZ | -0.11168671678088725 | -0.9937434665418967 | 0.11238988787472795 |
|
||||
| AR | -0.9791366197808846 | -0.2032030506760749 | 4.818513386109159 |
|
||||
| CA | -0.4979007493043646 | 0.8672340190756773 | -0.5741250208738828 |
|
||||
| CO | 0.6691553056325918 | 0.74312258540819 | 0.9004642286104536 |
|
||||
| CT | 0.914501128595007 | -0.4045833483949364 | -2.260352859856979 |
|
||||
| DE | -0.09980837850296802 | -0.9950066771537809 | 0.10030925499763493 |
|
||||
| DC | -0.976639481217665 | -0.21488444272885437 | 4.544952016140177 |
|
||||
| ... | ... | ... | ... |
|
||||
+----------------+----------------------+----------------------+---------------------+
|
||||
+----------------+-----------------+-----------------+-----------------+
|
||||
| Name: state | Name: foo | Name: bar | Name: baz |
|
||||
| Labels: | Labels: | Labels: | Labels: |
|
||||
| Type: []string | Type: []float64 | Type: []float64 | Type: []float64 |
|
||||
+----------------+-----------------+-----------------+-----------------+
|
||||
| AL | 0.6779842417 | 0.7350764367 | 0.9223316213 |
|
||||
| AK | 0.9096030926 | -0.4154782951 | -2.1892914822 |
|
||||
| AZ | -0.1116867167 | -0.9937434665 | 0.1123898878 |
|
||||
| AR | -0.9791366197 | -0.2032030506 | 4.8185133861 |
|
||||
| CA | -0.4979007493 | 0.867234019 | -0.5741250208 |
|
||||
| CO | 0.6691553056 | 0.7431225854 | 0.9004642286 |
|
||||
| CT | 0.9145011285 | -0.4045833483 | -2.2603528598 |
|
||||
| DE | -0.0998083785 | -0.9950066771 | 0.1003092549 |
|
||||
| DC | -0.9766394812 | -0.2148844427 | 4.5449520161 |
|
||||
| ... | ... | ... | ... |
|
||||
+----------------+-----------------+-----------------+-----------------+
|
||||
|
||||
|
||||
====== TEST DATA RESPONSE (arrow base64) ======
|
||||
FRAME=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
|
||||
FRAME=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
|
||||
|
||||
@@ -192,7 +192,10 @@ func getStateValues(t time.Time, field string, query usaQuery) []float64 {
|
||||
values := make([]float64, len(query.States))
|
||||
for i := range query.States {
|
||||
tv += incr
|
||||
values[i] = fn(float64(int64(tv) % int64(pn)))
|
||||
value := fn(float64(int64(tv) % int64(pn)))
|
||||
// We shall truncate float64 to a certain precision, because otherwise we might
|
||||
// get different cos/tan results across different CPU architectures and compilers.
|
||||
values[i] = float64(int(value*1e10)) / 1e10
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -15,20 +15,6 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { selectOptionInTest } from '@grafana/ui';
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
jest.mock('./api/alertmanager');
|
||||
jest.mock('./utils/config');
|
||||
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
import React from 'react';
|
||||
import { locationService, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import { PanelAlertTabContent } from './PanelAlertTabContent';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import {
|
||||
mockDataSource,
|
||||
MockDataSourceSrv,
|
||||
mockPromAlertingRule,
|
||||
mockPromRuleGroup,
|
||||
mockPromRuleNamespace,
|
||||
mockRulerGrafanaRule,
|
||||
} from './mocks';
|
||||
import { DataSourceType } from './utils/datasource';
|
||||
import { typeAsJestMock } from 'test/helpers/typeAsJestMock';
|
||||
import { getAllDataSources } from './utils/config';
|
||||
import { fetchRules } from './api/prometheus';
|
||||
import { fetchRulerRules } from './api/ruler';
|
||||
import { Annotation } from './utils/constants';
|
||||
import { byTestId } from 'testing-library-selector';
|
||||
import { PrometheusDatasource } from 'app/plugins/datasource/prometheus/datasource';
|
||||
import { DataSourceApi } from '@grafana/data';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
|
||||
jest.mock('./api/prometheus');
|
||||
jest.mock('./api/ruler');
|
||||
jest.mock('./utils/config');
|
||||
|
||||
const dataSources = {
|
||||
prometheus: mockDataSource({
|
||||
name: 'Prometheus',
|
||||
type: DataSourceType.Prometheus,
|
||||
}),
|
||||
};
|
||||
dataSources.prometheus.meta.alerting = true;
|
||||
|
||||
const mocks = {
|
||||
getAllDataSources: typeAsJestMock(getAllDataSources),
|
||||
api: {
|
||||
fetchRules: typeAsJestMock(fetchRules),
|
||||
fetchRulerRules: typeAsJestMock(fetchRulerRules),
|
||||
},
|
||||
};
|
||||
|
||||
const renderAlertTabContent = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||
const store = configureStore();
|
||||
|
||||
return render(
|
||||
<Provider store={store}>
|
||||
<Router history={locationService.getHistory()}>
|
||||
<PanelAlertTabContent dashboard={dashboard} panel={panel} />
|
||||
</Router>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const rules = [
|
||||
mockPromRuleNamespace({
|
||||
name: 'default',
|
||||
groups: [
|
||||
mockPromRuleGroup({
|
||||
name: 'mygroup',
|
||||
rules: [
|
||||
mockPromAlertingRule({
|
||||
name: 'dashboardrule1',
|
||||
annotations: {
|
||||
[Annotation.dashboardUID]: '12',
|
||||
[Annotation.panelID]: '34',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
mockPromRuleGroup({
|
||||
name: 'othergroup',
|
||||
rules: [
|
||||
mockPromAlertingRule({
|
||||
name: 'dashboardrule2',
|
||||
annotations: {
|
||||
[Annotation.dashboardUID]: '121',
|
||||
[Annotation.panelID]: '341',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const rulerRules = {
|
||||
default: [
|
||||
{
|
||||
name: 'mygroup',
|
||||
rules: [
|
||||
mockRulerGrafanaRule(
|
||||
{
|
||||
annotations: {
|
||||
[Annotation.dashboardUID]: '12',
|
||||
[Annotation.panelID]: '34',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'dashboardrule1',
|
||||
}
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'othergroup',
|
||||
rules: [
|
||||
mockRulerGrafanaRule(
|
||||
{
|
||||
annotations: {
|
||||
[Annotation.dashboardUID]: '121',
|
||||
[Annotation.panelID]: '341',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'dashboardrule2',
|
||||
}
|
||||
),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const dashboard = {
|
||||
uid: '12',
|
||||
time: {
|
||||
from: 'now-6h',
|
||||
to: 'now',
|
||||
},
|
||||
meta: {
|
||||
canSave: true,
|
||||
folderId: 1,
|
||||
folderTitle: 'super folder',
|
||||
},
|
||||
} as DashboardModel;
|
||||
const panel = ({
|
||||
datasource: dataSources.prometheus.uid,
|
||||
title: 'mypanel',
|
||||
editSourceId: 34,
|
||||
targets: [
|
||||
{
|
||||
expr: 'sum(some_metric [$__interval])) by (app)',
|
||||
refId: 'A',
|
||||
},
|
||||
],
|
||||
} as any) as PanelModel;
|
||||
|
||||
const ui = {
|
||||
row: byTestId('row'),
|
||||
createButton: byTestId<HTMLAnchorElement>('create-alert-rule-button'),
|
||||
};
|
||||
|
||||
describe('PanelAlertTabContent', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
|
||||
const dsService = new MockDataSourceSrv(dataSources);
|
||||
dsService.datasources[dataSources.prometheus.name] = new PrometheusDatasource(
|
||||
dataSources.prometheus
|
||||
) as DataSourceApi<any, any>;
|
||||
setDataSourceSrv(dsService);
|
||||
});
|
||||
|
||||
it('Will take into account panel maxDataPoints', async () => {
|
||||
await renderAlertTabContent(dashboard, ({
|
||||
...panel,
|
||||
maxDataPoints: 100,
|
||||
interval: '10s',
|
||||
} as any) as PanelModel);
|
||||
const button = await ui.createButton.find();
|
||||
const href = button.href;
|
||||
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
|
||||
expect(match).toHaveLength(2);
|
||||
const defaults = JSON.parse(decodeURIComponent(match![1]));
|
||||
expect(defaults.queries[0].model).toEqual({
|
||||
expr: 'sum(some_metric [5m])) by (app)',
|
||||
refId: 'A',
|
||||
datasource: 'Prometheus',
|
||||
interval: '',
|
||||
intervalMs: 300000,
|
||||
maxDataPoints: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it('Will take into account datasource minInterval', async () => {
|
||||
((getDatasourceSrv() as any) as MockDataSourceSrv).datasources[dataSources.prometheus.name].interval = '7m';
|
||||
|
||||
await renderAlertTabContent(dashboard, ({
|
||||
...panel,
|
||||
maxDataPoints: 100,
|
||||
} as any) as PanelModel);
|
||||
const button = await ui.createButton.find();
|
||||
const href = button.href;
|
||||
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
|
||||
expect(match).toHaveLength(2);
|
||||
const defaults = JSON.parse(decodeURIComponent(match![1]));
|
||||
expect(defaults.queries[0].model).toEqual({
|
||||
expr: 'sum(some_metric [7m])) by (app)',
|
||||
refId: 'A',
|
||||
datasource: 'Prometheus',
|
||||
interval: '',
|
||||
intervalMs: 420000,
|
||||
maxDataPoints: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
|
||||
mocks.api.fetchRules.mockResolvedValue(rules);
|
||||
mocks.api.fetchRulerRules.mockResolvedValue(rulerRules);
|
||||
|
||||
await renderAlertTabContent(dashboard, panel);
|
||||
|
||||
const rows = await ui.row.findAll();
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]).toHaveTextContent(/dashboardrule1/);
|
||||
expect(rows[0]).not.toHaveTextContent(/dashboardrule2/);
|
||||
const button = await ui.createButton.find();
|
||||
const href = button.href;
|
||||
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
|
||||
expect(match).toHaveLength(2);
|
||||
const defaults = JSON.parse(decodeURIComponent(match![1]));
|
||||
expect(defaults).toEqual({
|
||||
type: 'grafana',
|
||||
folder: { id: 1, title: 'super folder' },
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
queryType: '',
|
||||
relativeTimeRange: { from: 21600, to: 0 },
|
||||
datasourceUid: 'mock-ds-2',
|
||||
model: {
|
||||
expr: 'sum(some_metric [15s])) by (app)',
|
||||
refId: 'A',
|
||||
datasource: 'Prometheus',
|
||||
interval: '',
|
||||
intervalMs: 15000,
|
||||
},
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
datasourceUid: '-100',
|
||||
queryType: '',
|
||||
model: {
|
||||
refId: 'B',
|
||||
hide: false,
|
||||
type: 'classic_conditions',
|
||||
datasource: '__expr__',
|
||||
conditions: [
|
||||
{
|
||||
type: 'query',
|
||||
evaluator: { params: [3], type: 'gt' },
|
||||
operator: { type: 'and' },
|
||||
query: { params: ['A'] },
|
||||
reducer: { params: [], type: 'last' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
name: 'mypanel',
|
||||
condition: 'B',
|
||||
annotations: [
|
||||
{ key: '__dashboardUid__', value: '12' },
|
||||
{ key: '__panelId__', value: '34' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
+7
-3
@@ -1,9 +1,10 @@
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import React, { FC } from 'react';
|
||||
import { Alert, LinkButton } from '@grafana/ui';
|
||||
import { Alert, LinkButton, Button } from '@grafana/ui';
|
||||
import { panelToRuleFormValues } from '../../utils/rule-form';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { urlUtil } from '@grafana/data';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
@@ -12,8 +13,11 @@ interface Props {
|
||||
}
|
||||
|
||||
export const NewRuleFromPanelButton: FC<Props> = ({ dashboard, panel, className }) => {
|
||||
const formValues = panelToRuleFormValues(panel, dashboard);
|
||||
const { loading, value: formValues } = useAsync(() => panelToRuleFormValues(panel, dashboard), [panel, dashboard]);
|
||||
const location = useLocation();
|
||||
if (loading) {
|
||||
return <Button disabled={true}>Create alert rule from this panel</Button>;
|
||||
}
|
||||
|
||||
if (!formValues) {
|
||||
return (
|
||||
@@ -29,7 +33,7 @@ export const NewRuleFromPanelButton: FC<Props> = ({ dashboard, panel, className
|
||||
});
|
||||
|
||||
return (
|
||||
<LinkButton icon="bell" href={ruleFormUrl} className={className}>
|
||||
<LinkButton icon="bell" href={ruleFormUrl} className={className} data-testid="create-alert-rule-button">
|
||||
Create alert rule from this panel
|
||||
</LinkButton>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { DataSourceApi, DataSourceInstanceSettings, DataSourcePluginMeta, ScopedVars } from '@grafana/data';
|
||||
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
|
||||
import {
|
||||
GrafanaAlertStateDecision,
|
||||
GrafanaRuleDefinition,
|
||||
PromAlertingRuleState,
|
||||
PromRuleType,
|
||||
RulerGrafanaRuleDTO,
|
||||
} from 'app/types/unified-alerting-dto';
|
||||
import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace } from 'app/types/unified-alerting';
|
||||
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||
import { DataSourceSrv, GetDataSourceListFilters } from '@grafana/runtime';
|
||||
@@ -17,7 +23,7 @@ let nextDataSourceId = 1;
|
||||
export const mockDataSource = (
|
||||
partial: Partial<DataSourceInstanceSettings> = {},
|
||||
meta: Partial<DataSourcePluginMeta> = {}
|
||||
): DataSourceInstanceSettings => {
|
||||
): DataSourceInstanceSettings<any> => {
|
||||
const id = partial.id ?? nextDataSourceId++;
|
||||
|
||||
return {
|
||||
@@ -54,6 +60,40 @@ export const mockPromAlert = (partial: Partial<Alert> = {}): Alert => ({
|
||||
...partial,
|
||||
});
|
||||
|
||||
export const mockRulerGrafanaRule = (
|
||||
partial: Partial<RulerGrafanaRuleDTO> = {},
|
||||
partialDef: Partial<GrafanaRuleDefinition> = {}
|
||||
): RulerGrafanaRuleDTO => {
|
||||
return {
|
||||
for: '1m',
|
||||
grafana_alert: {
|
||||
uid: '123',
|
||||
title: 'myalert',
|
||||
namespace_uid: '123',
|
||||
namespace_id: 1,
|
||||
condition: 'A',
|
||||
no_data_state: GrafanaAlertStateDecision.Alerting,
|
||||
exec_err_state: GrafanaAlertStateDecision.Alerting,
|
||||
data: [
|
||||
{
|
||||
datasourceUid: '123',
|
||||
refId: 'A',
|
||||
queryType: 'huh',
|
||||
model: {} as any,
|
||||
},
|
||||
],
|
||||
...partialDef,
|
||||
},
|
||||
annotations: {
|
||||
message: 'alert with severity "{{.warning}}}"',
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
},
|
||||
...partial,
|
||||
};
|
||||
};
|
||||
|
||||
export const mockPromAlertingRule = (partial: Partial<AlertingRule> = {}): AlertingRule => {
|
||||
return {
|
||||
type: PromRuleType.Alerting,
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { DataQuery, rangeUtil, RelativeTimeRange } from '@grafana/data';
|
||||
import {
|
||||
DataQuery,
|
||||
rangeUtil,
|
||||
RelativeTimeRange,
|
||||
ScopedVars,
|
||||
getDefaultRelativeTimeRange,
|
||||
TimeRange,
|
||||
IntervalValues,
|
||||
} from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { getNextRefIdChar } from 'app/core/utils/query';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { ExpressionDatasourceID, ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource';
|
||||
import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||
import {
|
||||
Annotations,
|
||||
@@ -22,7 +29,6 @@ import { isGrafanaRulesSource } from './datasource';
|
||||
import { arrayToRecord, recordToArray } from './misc';
|
||||
import { isAlertingRulerRule, isGrafanaRulerRule } from './rules';
|
||||
import { parseInterval } from './time';
|
||||
import { getDefaultRelativeTimeRange } from '../../../../../../packages/grafana-data';
|
||||
|
||||
export const getDefaultFormValues = (): RuleFormValues =>
|
||||
Object.freeze({
|
||||
@@ -193,60 +199,81 @@ const getDefaultExpression = (refId: string): AlertQuery => {
|
||||
};
|
||||
};
|
||||
|
||||
const dataQueriesToGrafanaQueries = (
|
||||
const dataQueriesToGrafanaQueries = async (
|
||||
queries: DataQuery[],
|
||||
relativeTimeRange: RelativeTimeRange,
|
||||
datasourceName?: string
|
||||
): AlertQuery[] => {
|
||||
return queries.reduce<AlertQuery[]>((queries, target) => {
|
||||
scopedVars: ScopedVars | {},
|
||||
datasourceName?: string,
|
||||
maxDataPoints?: number,
|
||||
minInterval?: string
|
||||
): Promise<AlertQuery[]> => {
|
||||
const result: AlertQuery[] = [];
|
||||
for (const target of queries) {
|
||||
const dsName = target.datasource || datasourceName;
|
||||
const datasource = await getDataSourceSrv().get(dsName);
|
||||
|
||||
const range = rangeUtil.relativeToTimeRange(relativeTimeRange);
|
||||
const { interval, intervalMs } = getIntervals(range, minInterval ?? datasource.interval, maxDataPoints);
|
||||
const queryVariables = {
|
||||
__interval: { text: interval, value: interval },
|
||||
__interval_ms: { text: intervalMs, value: intervalMs },
|
||||
...scopedVars,
|
||||
};
|
||||
const interpolatedTarget = datasource.interpolateVariablesInQueries
|
||||
? await datasource.interpolateVariablesInQueries([target], queryVariables)[0]
|
||||
: target;
|
||||
if (dsName) {
|
||||
// expressions
|
||||
if (dsName === ExpressionDatasourceID) {
|
||||
const newQuery: AlertQuery = {
|
||||
refId: target.refId,
|
||||
refId: interpolatedTarget.refId,
|
||||
queryType: '',
|
||||
relativeTimeRange,
|
||||
datasourceUid: ExpressionDatasourceUID,
|
||||
model: target,
|
||||
model: interpolatedTarget,
|
||||
};
|
||||
return [...queries, newQuery];
|
||||
result.push(newQuery);
|
||||
// queries
|
||||
} else {
|
||||
const datasource = getDataSourceSrv().getInstanceSettings(target.datasource || datasourceName);
|
||||
if (datasource && datasource.meta.alerting) {
|
||||
const datasourceSettings = getDataSourceSrv().getInstanceSettings(dsName);
|
||||
if (datasourceSettings && datasourceSettings.meta.alerting) {
|
||||
const newQuery: AlertQuery = {
|
||||
refId: target.refId,
|
||||
queryType: target.queryType ?? '',
|
||||
refId: interpolatedTarget.refId,
|
||||
queryType: interpolatedTarget.queryType ?? '',
|
||||
relativeTimeRange,
|
||||
datasourceUid: datasource.uid,
|
||||
model: target,
|
||||
datasourceUid: datasourceSettings.uid,
|
||||
model: {
|
||||
...interpolatedTarget,
|
||||
maxDataPoints,
|
||||
intervalMs,
|
||||
},
|
||||
};
|
||||
return [...queries, newQuery];
|
||||
result.push(newQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
return queries;
|
||||
}, []);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const panelToRuleFormValues = (
|
||||
export const panelToRuleFormValues = async (
|
||||
panel: PanelModel,
|
||||
dashboard: DashboardModel
|
||||
): Partial<RuleFormValues> | undefined => {
|
||||
): Promise<Partial<RuleFormValues> | undefined> => {
|
||||
const { targets } = panel;
|
||||
|
||||
// it seems if default datasource is selected, datasource=null, hah
|
||||
const datasourceName =
|
||||
panel.datasource === null ? getDatasourceSrv().getInstanceSettings('default')?.name : panel.datasource;
|
||||
|
||||
if (!panel.editSourceId || !dashboard.uid) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const relativeTimeRange = rangeUtil.timeRangeToRelative(rangeUtil.convertRawToRange(dashboard.time));
|
||||
const queries = dataQueriesToGrafanaQueries(targets, relativeTimeRange, datasourceName);
|
||||
|
||||
const queries = await dataQueriesToGrafanaQueries(
|
||||
targets,
|
||||
relativeTimeRange,
|
||||
panel.scopedVars || {},
|
||||
panel.datasource ?? undefined,
|
||||
panel.maxDataPoints ?? undefined,
|
||||
panel.interval ?? undefined
|
||||
);
|
||||
// if no alerting capable queries are found, can't create a rule
|
||||
if (!queries.length || !queries.find((query) => query.datasourceUid !== ExpressionDatasourceUID)) {
|
||||
return undefined;
|
||||
@@ -269,6 +296,7 @@ export const panelToRuleFormValues = (
|
||||
: undefined,
|
||||
queries,
|
||||
name: panel.title,
|
||||
condition: queries[queries.length - 1].refId,
|
||||
annotations: [
|
||||
{
|
||||
key: Annotation.dashboardUID,
|
||||
@@ -282,3 +310,17 @@ export const panelToRuleFormValues = (
|
||||
};
|
||||
return formValues;
|
||||
};
|
||||
|
||||
export function getIntervals(range: TimeRange, lowLimit?: string, resolution?: number): IntervalValues {
|
||||
if (!resolution) {
|
||||
if (lowLimit && rangeUtil.intervalToMs(lowLimit) > 1000) {
|
||||
return {
|
||||
interval: lowLimit,
|
||||
intervalMs: rangeUtil.intervalToMs(lowLimit),
|
||||
};
|
||||
}
|
||||
return { interval: '1s', intervalMs: 1000 };
|
||||
}
|
||||
|
||||
return rangeUtil.calculateInterval(range, resolution, lowLimit);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useRef, useState, useLayoutEffect } from 'react';
|
||||
import { Icon, Tooltip } from '@grafana/ui';
|
||||
import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getLinkSrv } from '../../../panel/panellinks/link_srv';
|
||||
@@ -7,6 +7,7 @@ import { DashboardLink } from '../../state/DashboardModel';
|
||||
import { DashboardSearchHit } from 'app/features/search/types';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useAsync } from 'react-use';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
interface Props {
|
||||
link: DashboardLink;
|
||||
@@ -21,33 +22,45 @@ export const DashboardLinksDashboard: React.FC<Props> = (props) => {
|
||||
const [opened, setOpened] = useState(0);
|
||||
const resolvedLinks = useResolvedLinks(props, opened);
|
||||
|
||||
const buttonStyle = useStyles2(
|
||||
(theme) =>
|
||||
css`
|
||||
color: ${theme.colors.text.primary};
|
||||
`
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setDropdownCssClass(getDropdownLocationCssClass(listRef.current));
|
||||
}, [resolvedLinks]);
|
||||
|
||||
if (link.asDropdown) {
|
||||
return (
|
||||
<LinkElement link={link} key="dashlinks-dropdown" aria-label={selectors.components.DashboardLinks.dropDown}>
|
||||
<LinkElement link={link} key="dashlinks-dropdown" data-testid={selectors.components.DashboardLinks.dropDown}>
|
||||
<>
|
||||
<a
|
||||
<ToolbarButton
|
||||
onClick={() => setOpened(Date.now())}
|
||||
className="gf-form-label gf-form-label--dashlink"
|
||||
className={cx('gf-form-label gf-form-label--dashlink', buttonStyle)}
|
||||
data-placement="bottom"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={!!opened}
|
||||
aria-controls="dropdown-list"
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<Icon name="bars" style={{ marginRight: '4px' }} />
|
||||
<Icon aria-hidden name="bars" style={{ marginRight: '4px' }} />
|
||||
<span>{linkInfo.title}</span>
|
||||
</a>
|
||||
<ul className={`dropdown-menu ${dropdownCssClass}`} role="menu" ref={listRef}>
|
||||
</ToolbarButton>
|
||||
<ul id="dropdown-list" className={`dropdown-menu ${dropdownCssClass}`} role="menu" ref={listRef}>
|
||||
{resolvedLinks.length > 0 &&
|
||||
resolvedLinks.map((resolvedLink, index) => {
|
||||
return (
|
||||
<li key={`dashlinks-dropdown-item-${resolvedLink.id}-${index}`}>
|
||||
<li role="none" key={`dashlinks-dropdown-item-${resolvedLink.id}-${index}`}>
|
||||
<a
|
||||
role="menuitem"
|
||||
href={resolvedLink.url}
|
||||
target={link.targetBlank ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
data-testid={selectors.components.DashboardLinks.link}
|
||||
aria-label={`${resolvedLink.title} dashboard`}
|
||||
>
|
||||
{resolvedLink.title}
|
||||
</a>
|
||||
@@ -68,16 +81,17 @@ export const DashboardLinksDashboard: React.FC<Props> = (props) => {
|
||||
<LinkElement
|
||||
link={link}
|
||||
key={`dashlinks-list-item-${resolvedLink.id}-${index}`}
|
||||
aria-label={selectors.components.DashboardLinks.container}
|
||||
data-testid={selectors.components.DashboardLinks.container}
|
||||
>
|
||||
<a
|
||||
className="gf-form-label gf-form-label--dashlink"
|
||||
href={resolvedLink.url}
|
||||
target={link.targetBlank ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
data-testid={selectors.components.DashboardLinks.link}
|
||||
aria-label={`${resolvedLink.title} dashboard`}
|
||||
>
|
||||
<Icon name="apps" style={{ marginRight: '4px' }} />
|
||||
<Icon aria-hidden name="apps" style={{ marginRight: '4px' }} />
|
||||
<span>{resolvedLink.title}</span>
|
||||
</a>
|
||||
</LinkElement>
|
||||
@@ -89,7 +103,6 @@ export const DashboardLinksDashboard: React.FC<Props> = (props) => {
|
||||
|
||||
interface LinkElementProps {
|
||||
link: DashboardLink;
|
||||
'aria-label': string;
|
||||
key: string;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
PanelPluginDataSupport,
|
||||
ScopedVars,
|
||||
urlUtil,
|
||||
PanelModel as IPanelModel,
|
||||
DatasourceRef,
|
||||
} from '@grafana/data';
|
||||
import { EDIT_PANEL_ID } from 'app/core/constants';
|
||||
import config from 'app/core/config';
|
||||
@@ -124,7 +126,7 @@ const defaults: any = {
|
||||
title: '',
|
||||
};
|
||||
|
||||
export class PanelModel implements DataConfigSource {
|
||||
export class PanelModel implements DataConfigSource, IPanelModel {
|
||||
/* persisted id, used in URL to identify a panel */
|
||||
id!: number;
|
||||
editSourceId?: number;
|
||||
@@ -144,7 +146,7 @@ export class PanelModel implements DataConfigSource {
|
||||
panels?: any;
|
||||
declare targets: DataQuery[];
|
||||
transformations?: DataTransformerConfig[];
|
||||
datasource: string | null = null;
|
||||
datasource: DatasourceRef | null = null;
|
||||
thresholds?: any;
|
||||
pluginVersion?: string;
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
forceEscape,
|
||||
} = this.state;
|
||||
|
||||
const styles = getStyles(theme);
|
||||
const styles = getStyles(theme, wrapLogMessage);
|
||||
const hasData = logRows && logRows.length > 0;
|
||||
const hasUnescapedContent = this.checkUnescapedContent(logRows);
|
||||
|
||||
@@ -420,7 +420,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
|
||||
export const Logs = withTheme2(UnthemedLogs);
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean) => {
|
||||
return {
|
||||
noData: css`
|
||||
> * {
|
||||
@@ -450,7 +450,8 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
justify-content: space-between;
|
||||
`,
|
||||
logRows: css`
|
||||
overflow-x: scroll;
|
||||
overflow-x: ${wrapLogMessage ? 'unset' : 'scroll'};
|
||||
overflow-y: visible;
|
||||
width: 100%;
|
||||
`,
|
||||
infoText: css`
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import React, { memo } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
metaContainer: css`
|
||||
flex: 1;
|
||||
color: ${theme.colors.textWeak};
|
||||
margin-bottom: ${theme.spacing.d};
|
||||
color: ${theme.colors.text.secondary};
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
min-width: 30%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`,
|
||||
metaItem: css`
|
||||
margin-right: ${theme.spacing.d};
|
||||
margin-top: ${theme.spacing.xs};
|
||||
margin-right: ${theme.spacing(2)};
|
||||
margin-top: ${theme.spacing(0.5)};
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
.logs-meta-item__error {
|
||||
color: ${theme.palette.red};
|
||||
color: ${theme.colors.error.text};
|
||||
}
|
||||
`,
|
||||
metaLabel: css`
|
||||
margin-right: calc(${theme.spacing.d} / 2);
|
||||
font-size: ${theme.typography.size.sm};
|
||||
font-weight: ${theme.typography.weight.semibold};
|
||||
margin-right: calc(${theme.spacing(2)} / 2);
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
`,
|
||||
metaValue: css`
|
||||
font-family: ${theme.typography.fontFamily.monospace};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
font-family: ${theme.typography.fontFamilyMonospace};
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface MetaItemProps {
|
||||
}
|
||||
|
||||
export const MetaInfoItem = memo(function MetaInfoItem(props: MetaItemProps) {
|
||||
const style = useStyles(getStyles);
|
||||
const style = useStyles2(getStyles);
|
||||
const { label, value } = props;
|
||||
|
||||
return (
|
||||
@@ -55,7 +55,7 @@ export interface MetaInfoTextProps {
|
||||
}
|
||||
|
||||
export const MetaInfoText = memo(function MetaInfoText(props: MetaInfoTextProps) {
|
||||
const style = useStyles(getStyles);
|
||||
const style = useStyles2(getStyles);
|
||||
const { metaItems } = props;
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
DatasourceRef,
|
||||
isValidLiveChannelAddress,
|
||||
parseLiveChannelAddress,
|
||||
StreamingFrameOptions,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQueryType } from './types';
|
||||
import AnnotationQueryEditor from './components/AnnotationQueryEditor';
|
||||
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
|
||||
import { isString } from 'lodash';
|
||||
|
||||
let counter = 100;
|
||||
|
||||
@@ -37,7 +39,11 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
return json;
|
||||
},
|
||||
prepareQuery(anno: AnnotationQuery<GrafanaAnnotationQuery>): GrafanaQuery {
|
||||
return { ...anno, refId: anno.name, queryType: GrafanaQueryType.Annotations };
|
||||
let datasource: DatasourceRef | undefined | null = undefined;
|
||||
if (isString(anno.datasource)) {
|
||||
datasource = anno.datasource as DatasourceRef;
|
||||
}
|
||||
return { ...anno, refId: anno.name, queryType: GrafanaQueryType.Annotations, datasource };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -182,10 +182,12 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only applied on flux queries
|
||||
*/
|
||||
applyTemplateVariables(query: InfluxQuery, scopedVars: ScopedVars): Record<string, any> {
|
||||
// this only works in flux-mode, it should not be called in non-flux-mode
|
||||
if (!this.isFlux) {
|
||||
throw new Error('applyTemplateVariables called in influxql-mode. this should never happen');
|
||||
}
|
||||
|
||||
return {
|
||||
...query,
|
||||
query: this.templateSrv.replace(query.query ?? '', scopedVars), // The raw query text
|
||||
@@ -344,7 +346,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
|
||||
policy: this.templateSrv.replace(query.policy ?? '', scopedVars, 'regex'),
|
||||
};
|
||||
|
||||
if (query.rawQuery) {
|
||||
if (query.rawQuery || this.isFlux) {
|
||||
expandedQuery.query = this.templateSrv.replace(query.query ?? '', scopedVars, 'regex');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PanelModel, FieldConfigSource } from '@grafana/data';
|
||||
import { PanelModel, FieldConfigSource, DataQuery } from '@grafana/data';
|
||||
import { graphPanelMigrationHandler } from './GraphMigrations';
|
||||
|
||||
describe('Graph Panel Migrations', () => {
|
||||
@@ -52,7 +52,7 @@ describe('Graph Panel Migrations', () => {
|
||||
spaceLength: 10,
|
||||
stack: false,
|
||||
steppedLine: false,
|
||||
targets: [
|
||||
targets: ([
|
||||
{
|
||||
alias: 'Foo datacenter',
|
||||
labels: 'datacenter=foo,region=us-east-1',
|
||||
@@ -71,7 +71,7 @@ describe('Graph Panel Migrations', () => {
|
||||
refId: 'C',
|
||||
scenarioId: 'random_walk',
|
||||
},
|
||||
],
|
||||
] as unknown) as DataQuery[],
|
||||
thresholds: [],
|
||||
timeFrom: null,
|
||||
timeRegions: [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { LoadingState, MutableDataFrame, FieldType } from '@grafana/data';
|
||||
import { LoadingState, MutableDataFrame, FieldType, LogsSortOrder } from '@grafana/data';
|
||||
import { LogsPanel } from './LogsPanel';
|
||||
|
||||
type LogsPanelProps = ComponentProps<typeof LogsPanel>;
|
||||
@@ -38,6 +38,22 @@ describe('LogsPanel', () => {
|
||||
expect(screen.getByText(/common_app/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/common_job/i)).toBeInTheDocument();
|
||||
});
|
||||
it('shows common labels on top when descending sort order', () => {
|
||||
const { container } = setup({
|
||||
data: { series: seriesWithCommonLabels },
|
||||
options: { showCommonLabels: true, sortOrder: LogsSortOrder.Descending },
|
||||
});
|
||||
|
||||
expect(container.firstChild?.childNodes[0].textContent).toMatch(/^Common labels:common_appcommon_job/);
|
||||
});
|
||||
it('shows common labels on bottom when ascending sort order', () => {
|
||||
const { container } = setup({
|
||||
data: { series: seriesWithCommonLabels },
|
||||
options: { showCommonLabels: true, sortOrder: LogsSortOrder.Ascending },
|
||||
});
|
||||
|
||||
expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:common_appcommon_job$/);
|
||||
});
|
||||
it('does not show common labels when showCommonLabels is set to false', () => {
|
||||
setup({ data: { series: seriesWithCommonLabels }, options: { showCommonLabels: false } });
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { LogRows, CustomScrollbar, LogLabels, useStyles2 } from '@grafana/ui';
|
||||
import { PanelProps, Field, Labels, GrafanaTheme2 } from '@grafana/data';
|
||||
import { PanelProps, Field, Labels, GrafanaTheme2, LogsSortOrder } from '@grafana/data';
|
||||
import { Options } from './types';
|
||||
import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model';
|
||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||
@@ -24,7 +24,10 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||
},
|
||||
title,
|
||||
}) => {
|
||||
const style = useStyles2(getStyles(title));
|
||||
const isAscending = sortOrder === LogsSortOrder.Ascending;
|
||||
const style = useStyles2(getStyles(title, isAscending));
|
||||
const [scrollTop, setScrollTop] = useState(0);
|
||||
const logsContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Important to memoize stuff here, as panel rerenders a lot for example when resizing.
|
||||
const [logRows, deduplicatedRows, commonLabels] = useMemo(() => {
|
||||
@@ -35,6 +38,14 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||
return [logRows, deduplicatedRows, commonLabels];
|
||||
}, [data, dedupStrategy]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isAscending && logsContainerRef.current) {
|
||||
setScrollTop(logsContainerRef.current.offsetHeight);
|
||||
} else {
|
||||
setScrollTop(0);
|
||||
}
|
||||
}, [isAscending, logRows]);
|
||||
|
||||
const getFieldLinks = useCallback(
|
||||
(field: Field, rowIndex: number) => {
|
||||
return getFieldLinksForExplore({ field, rowIndex, range: data.timeRange });
|
||||
@@ -50,15 +61,17 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const renderCommonLabels = () => (
|
||||
<div className={style.labelContainer}>
|
||||
<span className={style.label}>Common labels:</span>
|
||||
<LogLabels labels={commonLabels ? (commonLabels.value as Labels) : { labels: '(no common labels)' }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHide>
|
||||
<div className={style.container}>
|
||||
{showCommonLabels && (
|
||||
<div className={style.labelContainer}>
|
||||
<span className={style.label}>Common labels:</span>
|
||||
<LogLabels labels={commonLabels ? (commonLabels.value as Labels) : { labels: '(no common labels)' }} />
|
||||
</div>
|
||||
)}
|
||||
<CustomScrollbar autoHide scrollTop={scrollTop}>
|
||||
<div className={style.container} ref={logsContainerRef}>
|
||||
{showCommonLabels && !isAscending && renderCommonLabels()}
|
||||
<LogRows
|
||||
logRows={logRows}
|
||||
deduplicatedRows={deduplicatedRows}
|
||||
@@ -71,20 +84,22 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||
getFieldLinks={getFieldLinks}
|
||||
logsSortOrder={sortOrder}
|
||||
enableLogDetails={enableLogDetails}
|
||||
previewLimit={isAscending ? logRows.length : undefined}
|
||||
/>
|
||||
{showCommonLabels && isAscending && renderCommonLabels()}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (title: string) => (theme: GrafanaTheme2) => ({
|
||||
const getStyles = (title: string, isAscending: boolean) => (theme: GrafanaTheme2) => ({
|
||||
container: css`
|
||||
margin-bottom: ${theme.spacing(1.5)};
|
||||
//We can remove this hot-fix when we fix panel menu with no title overflowing top of all panels
|
||||
margin-top: ${theme.spacing(!title ? 2.5 : 0)};
|
||||
`,
|
||||
labelContainer: css`
|
||||
margin: ${theme.spacing(0, 0, 0.5, 0.5)};
|
||||
margin: ${isAscending ? theme.spacing(0.5, 0, 0.5, 0) : theme.spacing(0, 0, 0.5, 0.5)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`,
|
||||
|
||||
@@ -489,6 +489,10 @@ Object {
|
||||
"fill": "dash",
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"scaleDistribution": Object {
|
||||
"log": 10,
|
||||
"type": "log",
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user