Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support GET queries by list and ID #10

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions redash/queries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package redash

import (
"encoding/json"
"io/ioutil"
"net/url"
"strconv"
"time"
)

// QueriesList models the response from Redash's /api/queries endpoint
type QueriesList struct {
Count int `json:"count"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Results []struct {
ID int `json:"id,omitempty"`
IsArchived bool `json:"is_archived,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
RetrievedAt time.Time `json:"retrieved_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Query string `json:"query,omitempty"`
QueryHash string `json:"query_hash,omitempty"`
Version int `json:"version,omitempty"`
LastModifiedByID int `json:"last_modified_by_id,omitempty"`
Tags []string `json:"tags,omitempty"`
APIKey string `json:"api_key,omitempty"`
DataSourceID int `json:"data_source_id,omitempty"`
LatestQueryDataID int `json:"latest_query_data_id,omitempty"`
Schedule QuerySchedule `json:"schedule,omitempty"`
User User `json:"user,omitempty"`
IsFavorite bool `json:"is_favorite,omitempty"`
IsDraft bool `json:"is_draft,omitempty"`
IsSafe bool `json:"is_safe,omitempty"`
Runtime float32 `json:"runtime,omitempty"`
Options QueryOptions `json:"options,omitempty"`
}
}

// Query models the response from Redash's /api/queries endpoint
type Query struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Query string `json:"query,omitempty"`
QueryHash string `json:"query_hash,omitempty"`
Version int `json:"version,omitempty"`
Schedule QuerySchedule `json:"schedule,omitempty"`
APIKey string `json:"api_key,omitempty"`
IsArchived bool `json:"is_archived,omitempty"`
IsDraft bool `json:"is_draft,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
DataSourceID int `json:"data_source_id,omitempty"`
LatestQueryDataID int `json:"latest_query_data_id,omitempty"`
Tags []string `json:"tags,omitempty"`
IsSafe bool `json:"is_safe,omitempty"`
User User `json:"user,omitempty"`
LastModifiedBy User `json:"last_modified_by,omitempty"`
IsFavorite bool `json:"is_favorite,omitempty"`
CanEdit bool `json:"can_edit,omitempty"`
Options QueryOptions `json:"options,omitempty"`
Visualizations []QueryVisualization `json:"visualizations,omitempty"`
}

// QuerySchedule struct
type QuerySchedule struct {
Interval int `json:"interval,omitempty"`
Time string `json:"time,omitempty"`
DayOfWeek string `json:"day_of_week,omitempty"`
Until interface{} `json:"until,omitempty"`
}

// QueryOptions struct
type QueryOptions struct {
Parameters []QueryOptionsParameter `json:"parameters,omitempty"`
}

// QueryOptionsParameter struct
type QueryOptionsParameter struct {
Title string `json:"title,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
EnumOptions string `json:"enum_options,omitempty"`
Locals []interface{} `json:"locals,omitempty"`
Value string `json:"value,omitempty"`
}

// QueryVisualization struct
type QueryVisualization struct {
ID int `json:"id,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Options QueryVisualizationOptions `json:"options,omitempty"`
}

// QueryVisualizationOptions struct
type QueryVisualizationOptions struct {
GlobalSeriesType string `json:"global_series_type,omitempty"`
SortX bool `json:"sort_x,omitempty"`
Legend QueryVisualizationLegendOptions `json:"legend,omitempty"`
YAxis []QueryAxisOptions `json:"y_axis,omitempty"`
XAxis QueryAxisOptions `json:"x_axis,omitempty"`
}

// QueryVisualizationLegendOptions struct
type QueryVisualizationLegendOptions struct {
Enabled bool `json:"enabled"`
Placement string `json:"placement"`
}

// QueryAxisOptions struct
type QueryAxisOptions struct {
Type string `json:"type,omitempty"`
Opposite bool `json:"opposite,omitempty"`
Labels QueryAxisLabelsOptions `json:"labels,omitempty"`
}

// QueryAxisLabelsOptions struct
type QueryAxisLabelsOptions struct {
Enabled bool `json:"enabled"`
}

// GetQueries returns a paginated list of queries
func (c *Client) GetQueries() (*QueriesList, error) {
path := "/api/queries"

queryParams := url.Values{}
response, err := c.get(path, queryParams)

if err != nil {
return nil, err
}
body, _ := ioutil.ReadAll(response.Body)

queries := QueriesList{}
err = json.Unmarshal(body, &queries)
if err != nil {
return nil, err
}

defer response.Body.Close()

return &queries, nil
}

// GetQuery gets a specific query
func (c *Client) GetQuery(id int) (*Query, error) {
path := "/api/queries/" + strconv.Itoa(id)

queryParams := url.Values{}
response, err := c.get(path, queryParams)
if err != nil {
return nil, err
}

defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}

query := Query{}

err = json.Unmarshal(body, &query)
if err != nil {
return nil, err
}

return &query, nil
}
86 changes: 86 additions & 0 deletions redash/queries_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package redash

import (
"io/ioutil"
"testing"
"time"

"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
)

func TestGetQueries(t *testing.T) {
assert := assert.New(t)
httpmock.Activate()
defer httpmock.DeactivateAndReset()

c, _ := NewClient(&Config{RedashURI: "https://com.acme/", APIKey: "ApIkEyApIkEyApIkEyApIkEyApIkEy"})

body, err := ioutil.ReadFile("testdata/get-queries.json")
if err != nil {
panic(err.Error())
}
httpmock.RegisterResponder("GET", "https://com.acme/api/queries",
httpmock.NewStringResponder(200, string(body)))

queries, err := c.GetQueries()
assert.Nil(err)

assert.Equal(3, queries.Count)
assert.Equal(1, queries.Page)
assert.Equal(10, queries.PageSize)
assert.Equal(3, len(queries.Results))
}

func TestGetQuery(t *testing.T) {
assert := assert.New(t)
httpmock.Activate()
defer httpmock.DeactivateAndReset()

c, _ := NewClient(&Config{RedashURI: "https://com.acme/", APIKey: "ApIkEyApIkEyApIkEyApIkEyApIkEy"})

body, err := ioutil.ReadFile("testdata/get-query.json")
if err != nil {
panic(err.Error())
}
httpmock.RegisterResponder("GET", "https://com.acme/api/queries/1",
httpmock.NewStringResponder(200, string(body)))

query, err := c.GetQuery(1)
assert.Nil(err)

assert.Equal(1, query.ID)
assert.Equal("Daily Active Users", query.Name)
assert.Equal("Service X DAU", query.Description)
assert.Equal("SELECT 1 + 1;", query.Query)
assert.Equal("ec2fda0cc5a54b38f81744fcad43ce5a", query.QueryHash)
assert.Equal(1, query.Version)
assert.Equal(false, query.IsArchived)
assert.Equal(false, query.IsDraft)
assert.Equal(true, query.IsSafe)
assert.Equal(false, query.IsFavorite)
assert.Equal(false, query.CanEdit)
assert.Equal(2, query.DataSourceID)
expectedUpdateAt, _ := time.Parse(time.RFC3339, "2021-11-07T22:22:34.929Z")
assert.Equal(expectedUpdateAt, query.UpdatedAt)
expectedCreatedAt, _ := time.Parse(time.RFC3339, "2021-08-13T23:29:12.743Z")
assert.Equal(expectedCreatedAt, query.CreatedAt)

assert.Equal(1, query.User.ID)
assert.Equal("Admin", query.User.Name)
assert.Equal("admin@example.com", query.User.Email)

assert.Equal(2, query.LastModifiedBy.ID)
assert.Equal("Developer", query.LastModifiedBy.Name)
assert.Equal("developer@example.com", query.LastModifiedBy.Email)

assert.Equal(2, len(query.Visualizations))
queryVisualisation1 := query.Visualizations[0]
assert.Equal(1, queryVisualisation1.ID)
assert.Equal("TABLE", queryVisualisation1.Type)
assert.Equal("Table", queryVisualisation1.Name)
queryVisualisation2 := query.Visualizations[1]
assert.Equal(2, queryVisualisation2.ID)
assert.Equal("CHART", queryVisualisation2.Type)
assert.Equal("DAU", queryVisualisation2.Name)
}
16 changes: 16 additions & 0 deletions redash/testdata/get-queries.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"count": 3,
"page": 1,
"page_size": 10,
"results": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
]
}
82 changes: 82 additions & 0 deletions redash/testdata/get-query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"id": 1,
"latest_query_data_id": 3919563,
"name": "Daily Active Users",
"description": "Service X DAU",
"query": "SELECT 1 + 1;",
"query_hash": "ec2fda0cc5a54b38f81744fcad43ce5a",
"schedule": null,
"api_key": "ApIkEyApIkEyApIkEyApIkEyApIkEy",
"is_archived": false,
"is_draft": false,
"updated_at": "2021-11-07T22:22:34.929Z",
"created_at": "2021-08-13T23:29:12.743Z",
"data_source_id": 2,
"options": { "parameters": [] },
"version": 1,
"tags": [],
"is_safe": true,
"user": {
"id": 1,
"name": "Admin",
"email": "admin@example.com",
"profile_image_url": "https://www.gravatar.com/avatar/example",
"groups": [2, 1, 3],
"updated_at": "2021-11-02T21:17:39.226Z",
"created_at": "2021-01-21T10:29:52.872Z",
"disabled_at": null,
"is_disabled": false,
"active_at": null,
"is_invitation_pending": false,
"is_email_verified": true,
"auth_type": "password"
},
"last_modified_by": {
"id": 2,
"name": "Developer",
"email": "developer@example.com",
"profile_image_url": "https://www.gravatar.com/avatar/example",
"groups": [2, 4, 4, 1],
"updated_at": "2021-11-16T23:34:36.829Z",
"created_at": "2021-03-13T23:46:09.191Z",
"disabled_at": null,
"is_disabled": false,
"active_at": "2021-11-02T08:09:03Z",
"is_invitation_pending": false,
"is_email_verified": true,
"auth_type": "password"
},
"visualizations": [
{
"id": 1,
"type": "TABLE",
"name": "Table",
"description": "",
"options": {},
"updated_at": "2021-03-13T23:29:12.747Z",
"created_at": "2021-03-13T23:29:12.747Z"
},
{
"id": 2,
"type": "CHART",
"name": "DAU",
"description": "",
"options": {
"yAxis": [{ "type": "linear" }, { "type": "linear", "opposite": true }],
"series": { "stacking": null },
"globalSeriesType": "line",
"sortX": true,
"seriesOptions": {
"DAU": { "zIndex": 0, "index": 0, "type": "line", "yAxis": 0 }
},
"xAxis": { "labels": { "enabled": true }, "type": "datetime" },
"columnMapping": { "Date": "x", "DAU": "y" },
"legend": { "enabled": true }
},
"updated_at": "2021-03-13T23:30:07.968Z",
"created_at": "2021-03-13T23:30:07.968Z"
}
],
"is_favorite": false,
"can_edit": false
}