From 87cc1263b15c2ca9220f988d6e758aea87207c7e Mon Sep 17 00:00:00 2001 From: Joe Paravisini Date: Wed, 23 Sep 2020 10:18:15 -0400 Subject: [PATCH 1/6] Updating release badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98c6848..055492d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Redash API Client # -[![Release][release-image]](releases) +[![Release](https://img.shields.io/github/v/release/snowplow-devops/redash-client-go)](releases) [![Actions Status](https://github.com/snowplow-devops/redash-client-go/workflows/ci/badge.svg)](https://github.com/snowplow-devops/redash-client-go/actions) [![Go Report Card][goreport-image]][goreport] [![License][license-image]][license] From 9132b4216381a58b92fed5815b6e8216f06e8f3e Mon Sep 17 00:00:00 2001 From: Joe Paravisini Date: Wed, 23 Sep 2020 10:20:49 -0400 Subject: [PATCH 2/6] Updating README badges --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 055492d..4a87c4a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # Redash API Client # -[![Release](https://img.shields.io/github/v/release/snowplow-devops/redash-client-go)](releases) +[![Release](https://img.shields.io/github/v/release/snowplow-devops/redash-client-go)](releases) + [![Actions Status](https://github.com/snowplow-devops/redash-client-go/workflows/ci/badge.svg)](https://github.com/snowplow-devops/redash-client-go/actions) -[![Go Report Card][goreport-image]][goreport] + +[![Go Report Card](https://goreportcard.com/badge/github.com/snowplow-devops/redash-client-go)](https://goreportcard.com/report/github.com/snowplow-devops/redash-client-go) + [![License][license-image]][license] ## Overview ## From 7b40053554795067c4596b2688cb0ffa5184e388 Mon Sep 17 00:00:00 2001 From: Joe Paravisini Date: Wed, 23 Sep 2020 10:28:10 -0400 Subject: [PATCH 3/6] Expanding example in README --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4a87c4a..d4f4833 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ # Redash API Client # [![Release](https://img.shields.io/github/v/release/snowplow-devops/redash-client-go)](releases) - [![Actions Status](https://github.com/snowplow-devops/redash-client-go/workflows/ci/badge.svg)](https://github.com/snowplow-devops/redash-client-go/actions) - [![Go Report Card](https://goreportcard.com/badge/github.com/snowplow-devops/redash-client-go)](https://goreportcard.com/report/github.com/snowplow-devops/redash-client-go) - [![License][license-image]][license] ## Overview ## @@ -17,14 +14,27 @@ A Simple API client library for interacting with Redash written in Go. In your go files, simply use: ```go -import "github.com/snowplow-devops/redash-client-go" +import "github.com/snowplow-devops/redash-client-go/redash" ``` Then next `go mod tidy` or `go test` invocation will automatically populate your `go.mod` with the last redash-client-go release, now [![Version](https://img.shields.io/github/tag/snowplow-devops/redash-client-go.svg)](https://github.com/snowplow-devops/redash-client-go/releases). +(Note you can use `go mod vendor` to vendor your dependencies.) -Note you can use `go mod vendor` to vendor your dependencies. +From there, you will need to setup a new client in order to access API methods: +``` +config := redash.Config{ + RedashURI: "https://acme.com/", + APIKey: "", +} + +c, err := redash.NewClient(&config) +if err != nil { + log.Fatal(fmt.Errorf("Error loading client: %q", err)) + return +} +``` ## Usage ## From f913ccc0d34af93683c2f019867d9e12f04522c9 Mon Sep 17 00:00:00 2001 From: Joe Paravisini Date: Wed, 23 Sep 2020 12:44:11 -0400 Subject: [PATCH 4/6] Updating README.md badges --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4f4833..9571902 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # Redash API Client # -[![Release](https://img.shields.io/github/v/release/snowplow-devops/redash-client-go)](releases) -[![Actions Status](https://github.com/snowplow-devops/redash-client-go/workflows/ci/badge.svg)](https://github.com/snowplow-devops/redash-client-go/actions) -[![Go Report Card](https://goreportcard.com/badge/github.com/snowplow-devops/redash-client-go)](https://goreportcard.com/report/github.com/snowplow-devops/redash-client-go) -[![License][license-image]][license] +[![Actions Status][actions-image]][actions] [![Go Report Card][goreport-image]][goreport] [![Release][release-image]][releases] [![License][license-image]][license] ## Overview ## @@ -80,10 +77,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -[travis-image]: https://travis-ci.com/snowplow-devops/redash-client-go.png?branch=master -[travis]: https://travis-ci.com/snowplow-devops/redash-client-go +[actions-image]: https://github.com/snowplow-devops/redash-client-go/workflows/ci/badge.svg +[actions]: https://github.com/snowplow-devops/redash-client-go/actions -[release-image]: http://img.shields.io/badge/release-0.1.0-6ad7e5.svg?style=flat +[release-image]: https://img.shields.io/github/v/release/snowplow-devops/redash-client-go?style=flat&color=6ad7e5 [releases]: https://github.com/snowplow-devops/redash-client-go/releases [license-image]: http://img.shields.io/badge/license-Apache--2-blue.svg?style=flat From 412a65de9b56f6253e70905e1e9c2c82a5dbea71 Mon Sep 17 00:00:00 2001 From: ohkinozomu Date: Mon, 16 Aug 2021 00:10:39 +0900 Subject: [PATCH 5/6] Add query to doRequest --- redash/client.go | 19 ++++++++++--------- redash/data_sources.go | 22 +++++++++++++--------- redash/groups.go | 28 +++++++++++++++++++--------- redash/users.go | 22 +++++++++++++++------- redash/users_test.go | 2 +- 5 files changed, 58 insertions(+), 35 deletions(-) diff --git a/redash/client.go b/redash/client.go index 292210c..9daa5d6 100644 --- a/redash/client.go +++ b/redash/client.go @@ -61,7 +61,7 @@ func (c *Client) IsStrict() bool { return c.Config.StrictMode } -func (c *Client) doRequest(method, path, body string) (*http.Response, error) { +func (c *Client) doRequest(method, path, body string, query url.Values) (*http.Response, error) { requestURI := strings.TrimSuffix(c.Config.RedashURI, "/") + path log.Debug(fmt.Sprintf("[DEBUG] %s request to %s", method, path)) @@ -74,6 +74,7 @@ func (c *Client) doRequest(method, path, body string) (*http.Response, error) { request.Header.Add("Content-Type", "application/json") request.Header.Set("Authorization", "Key "+c.Config.APIKey) + request.URL.RawQuery = query.Encode() return http.DefaultClient.Do(request) }() @@ -88,18 +89,18 @@ func (c *Client) doRequest(method, path, body string) (*http.Response, error) { return response, nil } -func (c *Client) get(path string) (*http.Response, error) { - return c.doRequest(http.MethodGet, path, "") +func (c *Client) get(path string, query url.Values) (*http.Response, error) { + return c.doRequest(http.MethodGet, path, "", query) } -func (c *Client) post(path string, payload string) (*http.Response, error) { - return c.doRequest(http.MethodPost, path, payload) +func (c *Client) post(path string, payload string, query url.Values) (*http.Response, error) { + return c.doRequest(http.MethodPost, path, payload, query) } -func (c *Client) put(path string, payload string) (*http.Response, error) { - return c.doRequest(http.MethodPut, path, payload) +func (c *Client) put(path string, payload string, query url.Values) (*http.Response, error) { + return c.doRequest(http.MethodPut, path, payload, query) } -func (c *Client) delete(path string) (*http.Response, error) { - return c.doRequest(http.MethodDelete, path, "") +func (c *Client) delete(path string, query url.Values) (*http.Response, error) { + return c.doRequest(http.MethodDelete, path, "", query) } diff --git a/redash/data_sources.go b/redash/data_sources.go index 1c4796f..ca82646 100644 --- a/redash/data_sources.go +++ b/redash/data_sources.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/url" "strconv" log "github.com/sirupsen/logrus" @@ -59,8 +60,8 @@ type DataSourceTypePropertyField struct { //GetDataSources gets an array of all DataSources available func (c *Client) GetDataSources() (*[]DataSource, error) { path := "/api/data_sources" - - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err @@ -80,8 +81,8 @@ func (c *Client) GetDataSources() (*[]DataSource, error) { //GetDataSource gets a specific DataSource func (c *Client) GetDataSource(id int) (*DataSource, error) { path := "/api/data_sources/" + strconv.Itoa(id) - - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err } @@ -105,8 +106,8 @@ func (c *Client) GetDataSource(id int) (*DataSource, error) { //GetDataSourceTypes gets all available types with configuration details func (c *Client) GetDataSourceTypes() ([]DataSourceType, error) { path := "/api/data_sources/types" - - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err @@ -193,7 +194,8 @@ func (c *Client) CreateDataSource(dataSourcePayload *DataSource) (*DataSource, e return nil, err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return nil, err } @@ -228,7 +230,8 @@ func (c *Client) UpdateDataSource(id int, dataSourcePayload *DataSource) (*DataS return nil, err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return nil, err } @@ -253,7 +256,8 @@ func (c *Client) UpdateDataSource(id int, dataSourcePayload *DataSource) (*DataS func (c *Client) DeleteDataSource(id int) error { path := "/api/data_sources/" + strconv.Itoa(id) - _, err := c.delete(path) + query := url.Values{} + _, err := c.delete(path, query) if err != nil { return err } diff --git a/redash/groups.go b/redash/groups.go index 6fda73b..5e28043 100644 --- a/redash/groups.go +++ b/redash/groups.go @@ -16,6 +16,7 @@ package redash import ( "encoding/json" "io/ioutil" + "net/url" "strconv" "time" ) @@ -48,7 +49,8 @@ type GroupCreatePayload struct { func (c *Client) GetGroups() (*[]Group, error) { path := "/api/groups" - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err @@ -69,7 +71,8 @@ func (c *Client) GetGroups() (*[]Group, error) { func (c *Client) GetGroup(id int) (*Group, error) { path := "/api/groups/" + strconv.Itoa(id) - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err } @@ -99,7 +102,8 @@ func (c *Client) CreateGroup(groupPayload *GroupCreatePayload) (*Group, error) { return nil, err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return nil, err } @@ -129,7 +133,8 @@ func (c *Client) UpdateGroup(id int, group *Group) (*Group, error) { return nil, err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return nil, err } @@ -152,7 +157,8 @@ func (c *Client) UpdateGroup(id int, group *Group) (*Group, error) { func (c *Client) DeleteGroup(id int) error { path := "/api/groups/" + strconv.Itoa(id) - _, err := c.delete(path) + query := url.Values{} + _, err := c.delete(path, query) if err != nil { return err } @@ -170,7 +176,8 @@ func (c *Client) GroupAddUser(groupID int, userID int) error { return err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return err } @@ -183,7 +190,8 @@ func (c *Client) GroupAddUser(groupID int, userID int) error { func (c *Client) GroupRemoveUser(groupID int, userID int) error { path := "/api/groups/" + strconv.Itoa(groupID) + "/members/" + strconv.Itoa(userID) - response, err := c.delete(path) + query := url.Values{} + response, err := c.delete(path, query) if err != nil { return err } @@ -202,7 +210,8 @@ func (c *Client) GroupAddDataSource(groupID int, dataSourceID int) error { return err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return err } @@ -215,7 +224,8 @@ func (c *Client) GroupAddDataSource(groupID int, dataSourceID int) error { func (c *Client) GroupRemoveDataSource(groupID int, dataSourceID int) error { path := "/api/groups/" + strconv.Itoa(groupID) + "/data_sources/" + strconv.Itoa(dataSourceID) - response, err := c.delete(path) + query := url.Values{} + response, err := c.delete(path, query) if err != nil { return err } diff --git a/redash/users.go b/redash/users.go index 8482671..73bac49 100644 --- a/redash/users.go +++ b/redash/users.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/url" "strconv" "time" ) @@ -80,7 +81,8 @@ type UserUpdatePayload struct { func (c *Client) GetUsers() (*UserList, error) { path := "/api/users" - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err @@ -102,7 +104,8 @@ func (c *Client) GetUsers() (*UserList, error) { func (c *Client) GetUser(id int) (*User, error) { path := "/api/users/" + strconv.Itoa(id) - response, err := c.get(path) + query := url.Values{} + response, err := c.get(path, query) if err != nil { return nil, err } @@ -132,7 +135,8 @@ func (c *Client) CreateUser(userCreatePayload *UserCreatePayload) (*User, error) return nil, err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return nil, err } @@ -162,7 +166,8 @@ func (c *Client) UpdateUser(id int, userUpdatePayload *UserUpdatePayload) (*User return nil, err } - response, err := c.post(path, string(payload)) + query := url.Values{} + response, err := c.post(path, string(payload), query) if err != nil { return nil, err } @@ -187,7 +192,8 @@ func (c *Client) UpdateUser(id int, userUpdatePayload *UserUpdatePayload) (*User func (c *Client) DisableUser(id int) error { path := "/api/users/" + strconv.Itoa(id) + "/disable" - response, err := c.post(path, "") + query := url.Values{} + response, err := c.post(path, "", query) if err != nil { return err } @@ -203,9 +209,11 @@ func (c *Client) DisableUser(id int) error { //SearchUsers finds a list of users matching a string (searches `name` and `email` fields) func (c *Client) SearchUsers(term string) (*UserList, error) { - path := "/api/users?q=" + term + path := "/api/users" - response, err := c.get(path) + query := url.Values{} + query.Add("q", term) + response, err := c.get(path, query) if err != nil { return nil, err diff --git a/redash/users_test.go b/redash/users_test.go index e6edcab..3de7e59 100644 --- a/redash/users_test.go +++ b/redash/users_test.go @@ -90,7 +90,7 @@ func TestGetUserByEmail(t *testing.T) { c, _ := NewClient(&Config{RedashURI: "https://com.acme/", APIKey: "ApIkEyApIkEyApIkEyApIkEyApIkEy"}) - httpmock.RegisterResponder("GET", "https://com.acme/api/users?q=tëst@email.com", + httpmock.RegisterResponder("GET", "https://com.acme/api/users?q=t%C3%ABst%40email.com", httpmock.NewStringResponder(200, `{"count": 1, "page": 1, "page_size": 25, "results": [ {"id": 1, "name": "Existing User", "email": "tëst@email.com"} ]}`)) httpmock.RegisterResponder("GET", "https://com.acme/api/users/1", From d6bcecfeab1f67a3dcaa8d8cce9be3ecefcd88cd Mon Sep 17 00:00:00 2001 From: Jonathan Du Date: Wed, 17 Nov 2021 12:18:43 +1100 Subject: [PATCH 6/6] Support getting queries by list and ID --- redash/queries.go | 174 +++++++++++++++++++++++++++++++ redash/queries_test.go | 86 +++++++++++++++ redash/testdata/get-queries.json | 16 +++ redash/testdata/get-query.json | 82 +++++++++++++++ 4 files changed, 358 insertions(+) create mode 100644 redash/queries.go create mode 100644 redash/queries_test.go create mode 100644 redash/testdata/get-queries.json create mode 100644 redash/testdata/get-query.json diff --git a/redash/queries.go b/redash/queries.go new file mode 100644 index 0000000..a0877a8 --- /dev/null +++ b/redash/queries.go @@ -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 +} diff --git a/redash/queries_test.go b/redash/queries_test.go new file mode 100644 index 0000000..52c8d27 --- /dev/null +++ b/redash/queries_test.go @@ -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) +} diff --git a/redash/testdata/get-queries.json b/redash/testdata/get-queries.json new file mode 100644 index 0000000..ad6c46f --- /dev/null +++ b/redash/testdata/get-queries.json @@ -0,0 +1,16 @@ +{ + "count": 3, + "page": 1, + "page_size": 10, + "results": [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ] +} diff --git a/redash/testdata/get-query.json b/redash/testdata/get-query.json new file mode 100644 index 0000000..7e935f4 --- /dev/null +++ b/redash/testdata/get-query.json @@ -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 +}