Skip to content

Commit 5e86208

Browse files
committed
feat:add alerting && support for new time variable format parsing
1 parent 23cf397 commit 5e86208

File tree

3 files changed

+109
-53
lines changed

3 files changed

+109
-53
lines changed

pkg/plugin/datasource.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (apiDatasource *APIDatasource) BuildClient(setting *APISource, param map[st
171171
}
172172
} else {
173173
keys := []string{"RegionId", "Region"}
174-
region1, exists := getValueIgnoreCase(param, keys)
174+
region1, exists := GetValueIgnoreCase(param, keys)
175175
region, _ := region1.(string)
176176
var endpoint string
177177
if exists {
@@ -190,35 +190,45 @@ func (apiDatasource *APIDatasource) BuildClient(setting *APISource, param map[st
190190
}
191191

192192
// 多层参数处理,eg array map
193-
func mapToQueryParams(data map[string]interface{}, prefix string, queries map[string]string) {
193+
func mapToQueryParams(data map[string]interface{}, prefix string, queries map[string]string, time backend.TimeRange) {
194194
for key, value := range data {
195195
newPrefix := key
196196
if prefix != "" {
197197
newPrefix = prefix + "." + key
198198
}
199-
processValue(value, newPrefix, queries)
199+
processValue(value, newPrefix, queries, time)
200200
}
201201
}
202202

203-
func processValue(value interface{}, prefix string, queries map[string]string) {
203+
func processValue(value interface{}, prefix string, queries map[string]string, time backend.TimeRange) {
204204
// log.DefaultLogger.Debug("processValue", "value", value, "prefix", prefix, "queries", queries)
205205
rValue := reflect.ValueOf(value)
206206
switch rValue.Kind() {
207207
case reflect.Map:
208208
// 如果是map,则递归处理
209209
for _, key := range rValue.MapKeys() {
210210
newPrefix := fmt.Sprintf("%s.%v", prefix, key.String())
211-
processValue(rValue.MapIndex(key).Interface(), newPrefix, queries)
211+
processValue(rValue.MapIndex(key).Interface(), newPrefix, queries, time)
212212
}
213213
case reflect.Slice, reflect.Array:
214214
// 如果是切片或数组,则遍历元素
215215
for i := 0; i < rValue.Len(); i++ {
216216
newPrefix := fmt.Sprintf("%s.%d", prefix, i+1)
217-
processValue(rValue.Index(i).Interface(), newPrefix, queries)
217+
processValue(rValue.Index(i).Interface(), newPrefix, queries, time)
218218
}
219219
default:
220220
// 其他类型,直接将值转换为字符串
221-
queries[prefix] = fmt.Sprintf("%v", value)
221+
formatStr := fmt.Sprintf("%v", value)
222+
if strings.Contains(formatStr, "${__time_from") || strings.Contains(formatStr, "${__time_to") {
223+
formatStrTime, err := FormatDateTimeForQuery(formatStr, time)
224+
if err != nil {
225+
log.DefaultLogger.Error("Time format error", err)
226+
break
227+
}
228+
formatStr = formatStrTime
229+
}
230+
queries[prefix] = formatStr
231+
log.DefaultLogger.Debug("processValue", "prefix", prefix, "value", formatStr)
222232
}
223233
}
224234

@@ -277,7 +287,7 @@ func (apiDatasource *APIDatasource) QueryAPI(ch chan Result, query backend.DataQ
277287
params := CreateApiInfo(queryInfo, setting)
278288

279289
queries := make(map[string]string)
280-
mapToQueryParams(queryInfo.Params, "", queries)
290+
mapToQueryParams(queryInfo.Params, "", queries, query.TimeRange)
281291

282292
log.DefaultLogger.Info("query params,", queries)
283293
log.DefaultLogger.Info("API params,", params)

pkg/plugin/utils.go

Lines changed: 90 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package plugin
22

33
import (
44
"fmt"
5+
"github.com/grafana/grafana-plugin-sdk-go/backend"
56
"regexp"
67
"strconv"
78
"strings"
89
"time"
910
)
1011

11-
// 判断不区分大小写的键是否存在于 map[string] 中,并获取对应的值
12-
func getValueIgnoreCase(data map[string]interface{}, keys []string) (interface{}, bool) {
12+
// GetValueIgnoreCase 判断不区分大小写的键是否存在于 map[string] 中,并获取对应的值
13+
func GetValueIgnoreCase(data map[string]interface{}, keys []string) (interface{}, bool) {
1314
for _, key := range keys {
1415
for k, v := range data {
1516
if strings.EqualFold(k, key) {
@@ -20,30 +21,79 @@ func getValueIgnoreCase(data map[string]interface{}, keys []string) (interface{}
2021
return "", false
2122
}
2223

23-
// 时间正则匹配
24-
// formatDateTime 根据给定的格式字符串(如 "${__from:date:iso}"、"${__from:date:iso+n}"、"${__from:date:iso-n}" 或 "${__from:date}")和时间戳,
25-
// 解析时区信息(如果存在)并应用到日期时间的格式化输出。
26-
func formatDateTime(formatStr string, timestamp time.Time) (string, error) {
27-
switch formatStr {
28-
case "${__from}":
29-
t := timestamp.String()
30-
return t, nil
31-
case "${__from:date:seconds}":
32-
t := timestamp.Unix()
33-
return strconv.FormatInt(t, 10), nil
34-
case "${__from:date:iso}":
35-
// 默认使用 UTC 时间
36-
t := timestamp.UTC()
37-
return t.Format(time.RFC3339), nil
24+
var datePatternRegex = regexp.MustCompile("(LT|LL?L?L?|l{1,4}|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|Q)")
25+
26+
var datePatternReplacements = map[string]string{
27+
"M": "1", // stdNumMonth 1 2 ... 11 12
28+
"MM": "01", // stdZeroMonth 01 02 ... 11 12
29+
"MMM": "Jan", // stdMonth Jan Feb ... Nov Dec
30+
"MMMM": "January", // stdLongMonth January February ... November December
31+
"D": "2", // stdDay 1 2 ... 30 30
32+
"DD": "02", // stdZeroDay 01 02 ... 30 31
33+
"DDD": "<stdDayOfYear>", // Day of the year 1 2 ... 364 365
34+
"DDDD": "<stdDayOfYearZero>", // Day of the year 001 002 ... 364 365 @todo****
35+
"d": "<stdDayOfWeek>", // Numeric representation of day of the week 0 1 ... 5 6
36+
"dd": "Mon", // ***Su Mo ... Fr Sa @todo
37+
"ddd": "Mon", // Sun Mon ... Fri Sat
38+
"dddd": "Monday", // stdLongWeekDay Sunday Monday ... Friday Saturday
39+
"e": "<stdDayOfWeek>", // Numeric representation of day of the week 0 1 ... 5 6 @todo
40+
"E": "<stdDayOfWeekISO>", // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0) 1 2 ... 6 7 @todo
41+
"w": "<stdWeekOfYear>", // 1 2 ... 52 53
42+
"ww": "<stdWeekOfYear>", // ***01 02 ... 52 53 @todo
43+
"W": "<stdWeekOfYear>", // 1 2 ... 52 53
44+
"WW": "<stdWeekOfYear>", // ***01 02 ... 52 53 @todo
45+
"YY": "06", // stdYear 70 71 ... 29 30
46+
"YYYY": "2006", // stdLongYear 1970 1971 ... 2029 2030
47+
"gg": "<stdIsoYearShort>", // ISO-8601 year number 70 71 ... 29 30
48+
"gggg": "<stdIsoYear>", // ***1970 1971 ... 2029 2030
49+
"GG": "<stdIsoYearShort>", // 70 71 ... 29 30
50+
"GGGG": "<stdIsoYear>", // ***1970 1971 ... 2029 2030
51+
"Q": "<stdQuarter>", // 1, 2, 3, 4
52+
"A": "PM", // stdPM AM PM
53+
"a": "pm", // stdpm am pm
54+
"H": "<stdHourNoZero>", // stdHour 0 1 ... 22 23
55+
"HH": "15", // 00 01 ... 22 23
56+
"h": "3", // stdHour12 1 2 ... 11 12
57+
"hh": "03", // stdZeroHour12 01 02 ... 11 12
58+
"m": "4", // stdZeroMinute 0 1 ... 58 59
59+
"mm": "04", // stdZeroMinute 00 01 ... 58 59
60+
"s": "5", // stdSecond 0 1 ... 58 59
61+
"ss": "05", // stdZeroSecond ***00 01 ... 58 59
62+
"z": "MST", // EST CST ... MST PST
63+
"zz": "MST", // EST CST ... MST PST
64+
"Z": "Z07:00", // stdNumColonTZ -07:00 -06:00 ... +06:00 +07:00
65+
"ZZ": "-0700", // stdNumTZ -0700 -0600 ... +0600 +0700
66+
"X": "<stdUnix>", // Seconds since unix epoch 1360013296
67+
"LT": "3:04 PM", // 8:30 PM
68+
"L": "01/02/2006", // 09/04/1986
69+
"l": "1/2/2006", // 9/4/1986
70+
"ll": "Jan 2 2006", // Sep 4 1986
71+
"lll": "Jan 2 2006 3:04 PM", // Sep 4 1986 8:30 PM
72+
"llll": "Mon, Jan 2 2006 3:04 PM", // Thu, Sep 4 1986 8:30 PM
73+
}
74+
75+
func patternToLayout(pattern string) string {
76+
var match [][]string
77+
if match = datePatternRegex.FindAllStringSubmatch(pattern, -1); match == nil {
78+
return pattern
79+
}
3880

39-
case "${__from:date}":
40-
// 使用本地时间
41-
t := timestamp.Local()
42-
return t.Format("2006-01-02 15:04:05"), nil
81+
for i := range match {
82+
if replace, ok := datePatternReplacements[match[i][0]]; ok {
83+
pattern = strings.Replace(pattern, match[i][0], replace, 1)
84+
}
85+
}
86+
87+
return pattern
88+
}
4389

44-
default:
45-
// 检查是否存在时区偏移量
46-
re := regexp.MustCompile(`\$\{__from:date:iso([+-])(-?\d+)\}`)
90+
// FormatDateTime 时间正则匹配: 根据给定的格式字符串和时间戳,
91+
// 解析时区信息(如果存在)并应用到日期时间的格式化输出。
92+
func FormatDateTime(formatStr string, timestamp time.Time) (string, error) {
93+
hasTimeOffset := strings.Contains(strings.ToLower(formatStr), "utc")
94+
loc := time.FixedZone("", 0)
95+
if hasTimeOffset {
96+
re := regexp.MustCompile(`.*?utc([+-])(\d+).*?`)
4797
matches := re.FindStringSubmatch(formatStr)
4898
fmt.Println("matches:", matches)
4999
if len(matches) == 3 {
@@ -64,34 +114,29 @@ func formatDateTime(formatStr string, timestamp time.Time) (string, error) {
64114
}
65115

66116
// 创建对应时区
67-
loc := time.FixedZone("", int(offsetDuration.Seconds()))
68-
69-
// 使用给定的时间戳和时区进行格式化
70-
t := timestamp.In(loc)
71-
return t.Format("2006-01-02 15:04:05"), nil
117+
loc = time.FixedZone("", int(offsetDuration.Seconds()))
72118
}
119+
}
73120

74-
return "", fmt.Errorf("unsupported format string: %s", formatStr)
121+
pattern := time.RFC3339
122+
re := regexp.MustCompile(`"(.+?)"`)
123+
matches := re.FindStringSubmatch(formatStr)
124+
if len(matches) > 1 {
125+
format := matches[1]
126+
pattern = patternToLayout(format)
75127
}
128+
129+
return timestamp.In(loc).Format(pattern), nil
76130
}
77131

78-
// formatDateTimeForQuery 根据给定的格式字符串、查询时间范围对象(queryTimeRange)和时间类型(fromOrTo),
132+
// FormatDateTimeForQuery 根据给定的格式字符串、查询时间范围对象(queryTimeRange)和时间类型(fromOrTo),
79133
// 格式化相应的时间(起始时间或结束时间)。
80-
func formatDateTimeForQuery(formatStr string, queryTimeRange QueryTimeRange, fromOrTo string) (string, error) {
134+
func FormatDateTimeForQuery(formatStr string, queryTimeRange backend.TimeRange) (string, error) {
81135
timestamp := queryTimeRange.From
82-
if fromOrTo == "to" {
136+
// 如果formatStr包含"__to",则使用结束时间,否则使用起始时间
137+
// 并全部替换为__from
138+
if strings.Contains(formatStr, "__time_to") {
83139
timestamp = queryTimeRange.To
84140
}
85-
86-
// 替换格式字符串中的 "__from" 为 "__to"
87-
if fromOrTo == "to" {
88-
formatStr = strings.ReplaceAll(formatStr, "${__from}", "${__to}")
89-
}
90-
91-
return formatDateTime(formatStr, timestamp)
92-
}
93-
94-
type QueryTimeRange struct {
95-
From time.Time
96-
To time.Time
141+
return FormatDateTime(formatStr, timestamp)
97142
}

src/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"id": "aliyun-openapi-grafana-datasource-plugin",
66
"metrics": true,
77
"backend": true,
8+
"alerting": true,
89
"executable": "gpx_pop",
910
"info": {
1011
"description": "",

0 commit comments

Comments
 (0)