Skip to content

Commit 82ed84c

Browse files
authored
Feat: add open in Sift button to grafana query builder (#31)
1 parent 2696237 commit 82ed84c

29 files changed

Lines changed: 4728 additions & 1971 deletions

.github/workflows/release.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# (For more information, see https://github.com/grafana/plugin-actions/blob/main/build-plugin/README.md)
33
name: Release
44

5-
65
on:
76
push:
87
tags:
@@ -22,4 +21,4 @@ jobs:
2221
- uses: grafana/plugin-actions/build-plugin@main
2322
with:
2423
policy_token: ${{ secrets.GRAFANA_ACCESS_POLICY_TOKEN }}
25-
attestation: true
24+
attestation: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ tmp/
3030
**/cypress/videos
3131
**/cypress/report.json
3232

33+
test-results/
34+
3335
# Editor
3436
.idea
3537
.vscode/*

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Enter these values when creating the datasource in Grafana.
3737
5. Query will be run after selection or clicking outside the input or by clicking the Refresh button
3838
6. Additional Queries can be added using the "Add Query" button to use both Channel and Calculated Channel queries in the same panel
3939

40-
4140
#### Selection Types
4241

4342
Selection of Assets, Runs, and Channels can be done using multiple different input types. Input type may be changed using the "Change input type" button.
@@ -79,7 +78,7 @@ the string value of the variable when the query is executed.
7978

8079
#### Caching
8180

82-
To improve performance, queries are cached by panel for a given query and associated interval (sample rate). If the
81+
To improve performance, queries are cached by panel for a given query and associated interval (sample rate). If the
8382
query range changes, but the query is otherwise the same, only the missing data on either side will be fetched. In the case
8483
where the right side of the query range is close to live, new data will always be queried for data between now and the last 10 minutes.
8584

package-lock.json

Lines changed: 3541 additions & 1787 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sift-grafana-datasource",
3-
"version": "1.2.12",
3+
"version": "1.3.0",
44
"description": "",
55
"scripts": {
66
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
@@ -12,7 +12,7 @@
1212
"lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx .",
1313
"lint:fix": "npm run lint -- --fix && prettier --write --list-different .",
1414
"lint:backend": "golangci-lint run --timeout=5m",
15-
"e2e": "playwright test",
15+
"e2": "echo disabled. original command: playright test",
1616
"server": "docker-compose up --build",
1717
"server:watch": "docker-compose up -d --build && (docker-compose logs -f & ./dev-watch.sh)",
1818
"sign": "npx --yes @grafana/sign-plugin@latest"
@@ -27,7 +27,6 @@
2727
"@grafana/plugin-e2e": "^1.17.1",
2828
"@grafana/plugin-meta-extractor": "^0.0.2",
2929
"@grafana/tsconfig": "^2.0.0",
30-
"@playwright/test": "1.52.0",
3130
"@stylistic/eslint-plugin-ts": "^2.9.0",
3231
"@swc/core": "^1.3.90",
3332
"@swc/helpers": "^0.5.0",
@@ -85,6 +84,7 @@
8584
"@grafana/runtime": "^11.5.3",
8685
"@grafana/schema": "^11.5.3",
8786
"@grafana/ui": "^11.5.3",
87+
"@playwright/test": "^1.52.0",
8888
"leven": "^4.0.0",
8989
"luxon": "^3.6.1",
9090
"nanoid": "^5.1.5",
@@ -99,4 +99,4 @@
9999
"form-data": "^4.0.4"
100100
},
101101
"packageManager": "npm@9.8.0"
102-
}
102+
}

pkg/plugin/datasource.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ func (d *SiftDatasource) CallResource(ctx context.Context, req *backend.CallReso
154154
case "purge-cache":
155155
return d.callPurgeCache(ctx, req, sender)
156156

157+
case "resolve-query-to-sift-metadata":
158+
return d.resolveQueryToSiftMetadata(ctx, req, sender)
159+
157160
default:
158161
return sender.Send(&backend.CallResourceResponse{
159162
Status: http.StatusNotFound,
@@ -192,7 +195,8 @@ func (d *SiftDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe
192195
}
193196

194197
type jsonData struct {
195-
Url string `json:"url"`
198+
Url string `json:"url"`
199+
FrontendUrl string `json:"frontendUrl"`
196200
}
197201

198202
type commonQueryProperties struct {
@@ -502,6 +506,18 @@ func generateQueries(pCtx backend.PluginContext, fqm queryModel, d *SiftDatasour
502506
return queries, calculatedChannelKeys, nil
503507
}
504508

509+
func sortedKeys(set map[string]struct{}) []string {
510+
if len(set) == 0 {
511+
return []string{}
512+
}
513+
result := make([]string, 0, len(set))
514+
for k := range set {
515+
result = append(result, k)
516+
}
517+
slices.Sort(result)
518+
return result
519+
}
520+
505521
func splitQueries(queries []siftApiGetDataSubQuery, chunkSize int) [][]siftApiGetDataSubQuery {
506522
var chunks [][]siftApiGetDataSubQuery
507523
for i := 0; i < len(queries); i += chunkSize {

pkg/plugin/resources.go

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
87
"net/http"
98
"net/url"
109
"regexp"
1110
"strconv"
1211
"strings"
1312

1413
"github.com/grafana/grafana-plugin-sdk-go/backend"
14+
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
1515
)
1616

1717
// Limit number of results that can be returned from the Sift API
@@ -167,3 +167,179 @@ func (d *SiftDatasource) callPurgeCache(ctx context.Context, req *backend.CallRe
167167
Body: []byte("{}"),
168168
})
169169
}
170+
171+
type calculatedChannelMetadata struct {
172+
Name string `json:"name"`
173+
SourceChannels []string `json:"sourceChannels"`
174+
Expression string `json:"expression"`
175+
ExpressionDataType string `json:"expressionDataType"`
176+
}
177+
178+
func (d *SiftDatasource) resolveQueryToSiftMetadata(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
179+
log.DefaultLogger.Debug("resolveQueryToSiftMetadata request", "body", string(req.Body))
180+
181+
queryModel, err := convertQueryIfNeeded(json.RawMessage(req.Body))
182+
if err != nil {
183+
log.DefaultLogger.Error("resolveQueryToSiftMetadata convert error", "error", err)
184+
return sender.Send(&backend.CallResourceResponse{
185+
Status: http.StatusBadRequest,
186+
Body: []byte(fmt.Sprintf("parse query: %s", err)),
187+
})
188+
}
189+
190+
queries, calculatedKeys, err := generateQueries(req.PluginContext, *queryModel, d)
191+
if err != nil {
192+
log.DefaultLogger.Error("resolveQueryToSiftMetadata generateQueries error", "error", err)
193+
return sender.Send(&backend.CallResourceResponse{
194+
Status: http.StatusBadRequest,
195+
Body: []byte(fmt.Sprintf("generate queries: %s", err)),
196+
})
197+
}
198+
199+
assetIDSet := make(map[string]struct{})
200+
runIDSet := make(map[string]struct{})
201+
channelIDSet := make(map[string]struct{})
202+
order := make([]string, 0, len(calculatedKeys))
203+
metaByName := make(map[string]*calculatedChannelMetadata)
204+
205+
for _, subQuery := range queries {
206+
calc := subQuery.CalculatedChannel
207+
if subQuery.Channel != nil {
208+
channelID := strings.TrimSpace(subQuery.Channel.ChannelId)
209+
if channelID != "" {
210+
channelIDSet[channelID] = struct{}{}
211+
}
212+
if subQuery.Channel.RunId != nil {
213+
if runID := strings.TrimSpace(*subQuery.Channel.RunId); runID != "" {
214+
runIDSet[runID] = struct{}{}
215+
}
216+
}
217+
}
218+
219+
if calc == nil {
220+
continue
221+
}
222+
223+
if calc.RunId != nil {
224+
if runID := strings.TrimSpace(*calc.RunId); runID != "" {
225+
runIDSet[runID] = struct{}{}
226+
}
227+
}
228+
229+
name := strings.TrimSpace(calc.ChannelKey)
230+
if key, ok := calculatedKeys[calc.ChannelKey]; ok {
231+
if trimmed := strings.TrimSpace(key.channelName); trimmed != "" {
232+
name = trimmed
233+
}
234+
}
235+
if name == "" {
236+
continue
237+
}
238+
239+
refs := calc.ExpressionRequest.ExpressionChannelReferences
240+
expr := strings.TrimSpace(calc.ExpressionRequest.Expression)
241+
242+
meta, exists := metaByName[name]
243+
if !exists {
244+
meta = &calculatedChannelMetadata{
245+
Name: name,
246+
SourceChannels: make([]string, len(refs)),
247+
ExpressionDataType: "double",
248+
}
249+
metaByName[name] = meta
250+
order = append(order, name)
251+
} else if len(meta.SourceChannels) != len(refs) {
252+
meta.SourceChannels = make([]string, len(refs))
253+
}
254+
255+
meta.Expression = expr
256+
257+
for idx, ref := range refs {
258+
channelID := strings.TrimSpace(ref.ChannelId)
259+
if channelID == "" {
260+
continue
261+
}
262+
channelIDSet[channelID] = struct{}{}
263+
if meta.SourceChannels[idx] == "" {
264+
meta.SourceChannels[idx] = channelID
265+
}
266+
}
267+
}
268+
269+
channelIDs := make([]string, 0, len(channelIDSet))
270+
for channelID := range channelIDSet {
271+
channelIDs = append(channelIDs, channelID)
272+
}
273+
274+
if len(channelIDs) > 0 {
275+
channels, err := d.getChannelsById(req.PluginContext, channelIDs)
276+
if err != nil {
277+
log.DefaultLogger.Error("resolveQueryToSiftMetadata channel lookup error", "error", err)
278+
return sender.Send(&backend.CallResourceResponse{
279+
Status: http.StatusBadRequest,
280+
Body: []byte(fmt.Sprintf("lookup channels: %s", err)),
281+
})
282+
}
283+
for _, channel := range channels {
284+
assetID := strings.TrimSpace(channel.AssetId)
285+
if assetID != "" {
286+
assetIDSet[assetID] = struct{}{}
287+
}
288+
}
289+
}
290+
291+
assetIDs := sortedKeys(assetIDSet)
292+
runIDs := sortedKeys(runIDSet)
293+
channelIDsSorted := sortedKeys(channelIDSet)
294+
295+
log.DefaultLogger.Debug(
296+
"resolveQueryToSiftMetadata summary",
297+
"assetIds", assetIDs,
298+
"runIds", runIDs,
299+
"channelIds", channelIDsSorted,
300+
)
301+
302+
var calculatedChannels []calculatedChannelMetadata
303+
for _, name := range order {
304+
meta := metaByName[name]
305+
if meta.Expression == "" {
306+
continue
307+
}
308+
309+
complete := true
310+
for _, channelID := range meta.SourceChannels {
311+
if channelID == "" {
312+
complete = false
313+
break
314+
}
315+
}
316+
if complete {
317+
calculatedChannels = append(calculatedChannels, *meta)
318+
}
319+
}
320+
321+
responsePayload := struct {
322+
AssetIDs []string `json:"assetIds"`
323+
RunIDs []string `json:"runIds"`
324+
ChannelIDs []string `json:"channelIds"`
325+
CalculatedChannels []calculatedChannelMetadata `json:"calculatedChannels,omitempty"`
326+
}{
327+
AssetIDs: assetIDs,
328+
RunIDs: runIDs,
329+
ChannelIDs: channelIDsSorted,
330+
CalculatedChannels: calculatedChannels,
331+
}
332+
333+
body, err := json.Marshal(responsePayload)
334+
if err != nil {
335+
return sender.Send(&backend.CallResourceResponse{
336+
Status: http.StatusInternalServerError,
337+
Body: []byte(fmt.Sprintf("encode response: %s", err)),
338+
})
339+
}
340+
341+
return sender.Send(&backend.CallResourceResponse{
342+
Status: http.StatusOK,
343+
Body: body,
344+
})
345+
}

playwright-report/index.html

Lines changed: 0 additions & 77 deletions
This file was deleted.

playwright.config.ts.disabled

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ export default defineConfig({
4949
dependencies: ['auth'],
5050
},
5151
],
52-
});
52+
});

playwright/.auth/admin.json

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)