From dcac383150304477440ec788bce7fb2b0e537484 Mon Sep 17 00:00:00 2001 From: winebarrel Date: Mon, 10 Jun 2024 22:56:34 +0900 Subject: [PATCH 1/5] Add WaitQueryJSON --- README.md | 29 +++++++------------------- query.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ query_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 68c4121..d9c4bc5 100644 --- a/README.md +++ b/README.md @@ -64,27 +64,10 @@ func main() { panic(err) } - if job != nil { - for { - job, err := client.GetJob(ctx, job.Job.ID) + err = client.WaitQueryJSON(ctx, query.ID, job, nil, &buf) - if err != nil { - panic(err) - } - - if job.Job.Status != redash.JobStatusPending && job.Job.Status != redash.JobStatusStarted { - buf = bytes.Buffer{} - err := client.GetQueryResultsJSON(ctx, query.ID, &buf) - - if err != nil { - panic(err) - } - - break - } - - time.Sleep(1 * time.Second) - } + if err != nil { + panic(err) } fmt.Println(buf.String()) @@ -106,8 +89,10 @@ if err != nil { panic(err) } -if job != nil { - // Waiting... +err = client.WaitQueryJSON(ctx, query.ID, job, nil, &buf) + +if err != nil { + panic(err) } out, err := client.GetQueryResultsStruct(context.Background(), query.ID) diff --git a/query.go b/query.go index 336676e..9a5ec60 100644 --- a/query.go +++ b/query.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "slices" "time" "github.com/winebarrel/redash-go/v2/internal/util" @@ -344,6 +345,63 @@ func (client *Client) ExecQueryJSON(ctx context.Context, id int, input *ExecQuer return nil, err } +var ( + defaultWaitQueryJSONOptionWaitStatuses = []int{ + JobStatusPending, + JobStatusStarted, + } +) + +const ( + defaultWaitQueryJSONOptionInterval = 1 * time.Second +) + +type WaitQueryJSONOption struct { + WaitStatuses []int + Interval time.Duration +} + +func (client *Client) WaitQueryJSON(ctx context.Context, queryId int, job *JobResponse, option *WaitQueryJSONOption, out io.Writer) error { + if job == nil || job.Job.ID == "" { + return nil + } + + waitStatus := defaultWaitQueryJSONOptionWaitStatuses + interval := defaultWaitQueryJSONOptionInterval + + if option != nil { + if len(option.WaitStatuses) > 0 { + waitStatus = option.WaitStatuses + } + + if option.Interval > 0 { + interval = option.Interval + } + } + + for { + job, err := client.GetJob(ctx, job.Job.ID) + + if err != nil { + return err + } + + if !slices.Contains(waitStatus, job.Job.Status) { + err := client.GetQueryResultsJSON(ctx, queryId, out) + + if err != nil { + return err + } + + break + } + + time.Sleep(interval) + } + + return nil +} + type QueryTags struct { Tags []QueryTagsTag `json:"tags"` } diff --git a/query_test.go b/query_test.go index 84c004b..88bddd9 100644 --- a/query_test.go +++ b/query_test.go @@ -1980,3 +1980,56 @@ func Test_Query_IgnoreCache_Acc(t *testing.T) { assert.NotEqual(cachedNow, now) } } + +func Test_WaitQueryJSON_Acc(t *testing.T) { + if !testAcc { + t.Skip() + } + + assert := assert.New(t) + require := require.New(t) + client, _ := redash.NewClient(testRedashEndpoint, testRedashAPIKey) + ds, err := client.CreateDataSource(context.Background(), &redash.CreateDataSourceInput{ + Name: "test-postgres-1", + Type: "pg", + Options: map[string]any{ + "dbname": "postgres", + "host": "postgres", + "port": 5432, + "user": "postgres", + }, + }) + require.NoError(err) + + defer func() { + client.DeleteDataSource(context.Background(), ds.ID) //nolint:errcheck + }() + + _, err = client.ListQueries(context.Background(), nil) + require.NoError(err) + + query, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{ + DataSourceID: ds.ID, + Name: "test-query-1", + Query: "select 1", + }) + require.NoError(err) + assert.Equal("test-query-1", query.Name) + + var buf bytes.Buffer + job, err := client.ExecQueryJSON(context.Background(), query.ID, nil, &buf) + require.NoError(err) + err = client.WaitQueryJSON(context.Background(), query.ID, job, nil, &buf) + require.NoError(err) + assert.True(strings.HasPrefix(buf.String(), `{"query_result"`)) + + buf.Reset() + job, err = client.ExecQueryJSON(context.Background(), query.ID, nil, &buf) + require.NoError(err) + err = client.WaitQueryJSON(context.Background(), query.ID, job, &redash.WaitQueryJSONOption{ + WaitStatuses: []int{redash.JobStatusPending, redash.JobStatusStarted}, + Interval: 500 * time.Microsecond, + }, &buf) + require.NoError(err) + assert.True(strings.HasPrefix(buf.String(), `{"query_result"`)) +} From a65b5b677d95cf92886968942c9033847054a48a Mon Sep 17 00:00:00 2001 From: winebarrel Date: Mon, 10 Jun 2024 22:57:47 +0900 Subject: [PATCH 2/5] Fix README --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index d9c4bc5..c5650c4 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,6 @@ input := &redash.ExecQueryJSONInput{ WithoutOmittingMaxAge: true, } -// If `max_age=0`, no result is returned. -// Results should be obtained with the GetQueryResultsXXX method. job, err := client.ExecQueryJSON(ctx, query.ID, input, nil) if err != nil { @@ -95,13 +93,7 @@ if err != nil { panic(err) } -out, err := client.GetQueryResultsStruct(context.Background(), query.ID) - -if err != nil { - panic(err) -} - -fmt.Println(out) +fmt.Println(buf.String()) ``` ### Set debug mode From 0abde47127114bddda007a1fbde9cc55ae186e44 Mon Sep 17 00:00:00 2001 From: winebarrel Date: Mon, 10 Jun 2024 22:59:25 +0900 Subject: [PATCH 3/5] Run "make gen" --- query_without_ctx.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/query_without_ctx.go b/query_without_ctx.go index d08a76b..18bfd64 100644 --- a/query_without_ctx.go +++ b/query_without_ctx.go @@ -55,6 +55,10 @@ func (client *ClientWithoutContext) ExecQueryJSON(id int, input *ExecQueryJSONIn return client.withCtx.ExecQueryJSON(context.Background(), id, input, out) } +func (client *ClientWithoutContext) WaitQueryJSON(queryId int, job *JobResponse, option *WaitQueryJSONOption, out io.Writer) error { + return client.withCtx.WaitQueryJSON(context.Background(), queryId, job, option, out) +} + func (client *ClientWithoutContext) GetQueryTags() (*QueryTags, error) { return client.withCtx.GetQueryTags(context.Background()) } From 004ec7319c20693df5df02ca37a6817dd2203319 Mon Sep 17 00:00:00 2001 From: winebarrel Date: Mon, 10 Jun 2024 23:09:41 +0900 Subject: [PATCH 4/5] Add unit test --- query_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/query_test.go b/query_test.go index 88bddd9..49c5e94 100644 --- a/query_test.go +++ b/query_test.go @@ -1025,6 +1025,76 @@ func Test_ExecQueryJSON_ReturnJob(t *testing.T) { }, job) } +func Test_WaitQueryJSON(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) { + assert.Equal( + http.Header( + http.Header{ + "Authorization": []string{"Key " + testRedashAPIKey}, + "Content-Type": []string{"application/json"}, + "User-Agent": []string{"redash-go"}, + }, + ), + req.Header, + ) + require.NotNil(req.Body) + body, _ := io.ReadAll(req.Body) + assert.Equal(`{}`, string(body)) + return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null, "status": 1, "updated_at": 0}}`), nil + }) + + httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) { + assert.Equal( + http.Header( + http.Header{ + "Authorization": []string{"Key " + testRedashAPIKey}, + "Content-Type": []string{"application/json"}, + "User-Agent": []string{"redash-go"}, + }, + ), + req.Header, + ) + return httpmock.NewStringResponse(http.StatusOK, ` + { + "job": { + "error": "", + "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", + "query_result_id": 1, + "status": 3, + "updated_at": 0 + } + } + `), nil + }) + + httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) { + assert.Equal( + http.Header( + http.Header{ + "Authorization": []string{"Key " + testRedashAPIKey}, + "Content-Type": []string{"application/json"}, + "User-Agent": []string{"redash-go"}, + }, + ), + req.Header, + ) + return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil + }) + + client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey) + var buf bytes.Buffer + job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf) + assert.NoError(err) + err = client.WaitQueryJSON(context.Background(), 1, job, nil, &buf) + assert.NoError(err) + assert.Equal(`{"foo":"bar"}`, buf.String()) +} + func Test_GetQueryTags_OK(t *testing.T) { assert := assert.New(t) httpmock.Activate() From fef57ec16394045f140b7d8ff1af6921341e8bac Mon Sep 17 00:00:00 2001 From: winebarrel Date: Mon, 10 Jun 2024 23:14:05 +0900 Subject: [PATCH 5/5] Update codecov.yml --- codecov.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/codecov.yml b/codecov.yml index 79da8c4..7ec3c1a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,11 @@ ignore: - ".*_without_ctx\\.go" - "tools" +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true