From efcdc1a97883b2b5f4a83ea5edc5138be0bc1a78 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Fri, 18 Mar 2022 16:56:45 +0530 Subject: [PATCH 01/10] Enable WithEvent to send an event to the Amazon EventBridge event bus after the SQL statement runs --- pkg/redshift/api/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/redshift/api/api.go b/pkg/redshift/api/api.go index e957b81f..7a5884d8 100644 --- a/pkg/redshift/api/api.go +++ b/pkg/redshift/api/api.go @@ -89,6 +89,7 @@ func (c *API) Execute(ctx context.Context, input *api.ExecuteQueryInput) (*api.E DbUser: commonInput.DbUser, SecretArn: commonInput.SecretARN, Sql: aws.String(input.Query), + WithEvent: aws.Bool(true), } output, err := c.DataClient.ExecuteStatementWithContext(ctx, redshiftInput) From 6b99e50bda0081a2ef9ec97d4cf911bfa7e57567 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Mon, 21 Mar 2022 18:31:01 +0530 Subject: [PATCH 02/10] Add WithEvent as a data source setting --- pkg/redshift/api/api.go | 6 ++++-- pkg/redshift/api/api_test.go | 29 +++++++++++++++++++++++------ pkg/redshift/models/settings.go | 1 + 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/pkg/redshift/api/api.go b/pkg/redshift/api/api.go index 7a5884d8..29baebeb 100644 --- a/pkg/redshift/api/api.go +++ b/pkg/redshift/api/api.go @@ -66,12 +66,14 @@ type apiInput struct { Database *string DbUser *string SecretARN *string + WithEvent *bool } func (c *API) apiInput() apiInput { res := apiInput{ ClusterIdentifier: aws.String(c.settings.ClusterIdentifier), Database: aws.String(c.settings.Database), + WithEvent: aws.Bool(c.settings.WithEvent), } if c.settings.UseManagedSecret { res.SecretARN = aws.String(c.settings.ManagedSecret.ARN) @@ -89,7 +91,7 @@ func (c *API) Execute(ctx context.Context, input *api.ExecuteQueryInput) (*api.E DbUser: commonInput.DbUser, SecretArn: commonInput.SecretARN, Sql: aws.String(input.Query), - WithEvent: aws.Bool(true), + WithEvent: commonInput.WithEvent, } output, err := c.DataClient.ExecuteStatementWithContext(ctx, redshiftInput) @@ -353,7 +355,7 @@ func (c *API) Clusters() ([]models.RedshiftCluster, error) { } res := []models.RedshiftCluster{} for _, r := range out.Clusters { - if (r != nil && r.ClusterIdentifier != nil && r.Endpoint != nil && r.Endpoint.Address != nil && r.Endpoint.Port != nil && r.DBName != nil) { + if r != nil && r.ClusterIdentifier != nil && r.Endpoint != nil && r.Endpoint.Address != nil && r.Endpoint.Port != nil && r.DBName != nil { res = append(res, models.RedshiftCluster{ ClusterIdentifier: *r.ClusterIdentifier, Endpoint: models.RedshiftEndpoint{ diff --git a/pkg/redshift/api/api_test.go b/pkg/redshift/api/api_test.go index 0560fd30..802e61c5 100644 --- a/pkg/redshift/api/api_test.go +++ b/pkg/redshift/api/api_test.go @@ -33,6 +33,7 @@ func Test_apiInput(t *testing.T) { ClusterIdentifier: aws.String("cluster"), Database: aws.String("db"), DbUser: aws.String("user"), + WithEvent: aws.Bool(false), }, }, { @@ -49,6 +50,22 @@ func Test_apiInput(t *testing.T) { ClusterIdentifier: aws.String("cluster"), Database: aws.String("db"), SecretARN: aws.String("arn:..."), + WithEvent: aws.Bool(false), + }, + }, + { + "with event", + &models.RedshiftDataSourceSettings{ + WithEvent: true, + ClusterIdentifier: "cluster", + Database: "db", + DBUser: "user", + }, + apiInput{ + WithEvent: aws.Bool(true), + ClusterIdentifier: aws.String("cluster"), + Database: aws.String("db"), + DbUser: aws.String("user"), }, }, } @@ -250,14 +267,14 @@ func Test_GetClusters(t *testing.T) { expectedClusters: []models.RedshiftCluster{*expectedCluster1, *expectedCluster2}, }, { - c: errC, - desc: "Error with DescribeCluster", - errMsg: "Boom!", + c: errC, + desc: "Error with DescribeCluster", + errMsg: "Boom!", }, { - c: nilC, - desc: "DescribeCluster returned nil", - errMsg: "missing clusters content", + c: nilC, + desc: "DescribeCluster returned nil", + errMsg: "missing clusters content", }, } for _, tt := range tests { diff --git a/pkg/redshift/models/settings.go b/pkg/redshift/models/settings.go index 4104297d..4cc85686 100644 --- a/pkg/redshift/models/settings.go +++ b/pkg/redshift/models/settings.go @@ -37,6 +37,7 @@ type RedshiftDataSourceSettings struct { ClusterIdentifier string `json:"clusterIdentifier"` Database string `json:"database"` UseManagedSecret bool `json:"useManagedSecret"` + WithEvent bool `json:"withEvent"` DBUser string `json:"dbUser"` ManagedSecret ManagedSecret } From 97453b0bc0106742e7afa5789bede1f7b4ac5414 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Mon, 21 Mar 2022 18:43:21 +0530 Subject: [PATCH 03/10] Add WithEvent as a data source setting to the frontend --- src/ConfigEditor/ConfigEditor.tsx | 17 +++++++++++++++++ src/types.ts | 1 + 2 files changed, 18 insertions(+) diff --git a/src/ConfigEditor/ConfigEditor.tsx b/src/ConfigEditor/ConfigEditor.tsx index 9cac2a74..9f963579 100644 --- a/src/ConfigEditor/ConfigEditor.tsx +++ b/src/ConfigEditor/ConfigEditor.tsx @@ -11,6 +11,8 @@ import { RedshiftManagedSecret, } from '../types'; import { AuthTypeSwitch } from './AuthTypeSwitch'; +import { selectors } from 'selectors'; +import { InlineField, Switch } from '@grafana/ui'; export type Props = DataSourcePluginOptionsEditorProps; @@ -209,6 +211,21 @@ export function ConfigEditor(props: Props) { label={selectors.components.ConfigEditor.Database.input} data-testid={selectors.components.ConfigEditor.Database.testID} /> + + + props.onOptionsChange({ + ...props.options, + jsonData: { + ...props.options.jsonData, + withEvent: e.currentTarget.checked, + }, + }) + } + css={undefined} + /> + ); } diff --git a/src/types.ts b/src/types.ts index e662dc57..0bbff717 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,6 +47,7 @@ export const defaultQuery: Partial = { * These are options configured for each DataSource instance */ export interface RedshiftDataSourceOptions extends AwsAuthDataSourceJsonData { + withEvent?: boolean; useManagedSecret?: boolean; clusterIdentifier?: string; database?: string; From 11caa318b870816681fe8ddd1389bebdb0e63080 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Mon, 21 Mar 2022 22:21:22 +0530 Subject: [PATCH 04/10] Remove redundant selectors import --- src/ConfigEditor/ConfigEditor.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ConfigEditor/ConfigEditor.tsx b/src/ConfigEditor/ConfigEditor.tsx index 9f963579..7ca8b9a6 100644 --- a/src/ConfigEditor/ConfigEditor.tsx +++ b/src/ConfigEditor/ConfigEditor.tsx @@ -11,7 +11,6 @@ import { RedshiftManagedSecret, } from '../types'; import { AuthTypeSwitch } from './AuthTypeSwitch'; -import { selectors } from 'selectors'; import { InlineField, Switch } from '@grafana/ui'; export type Props = DataSourcePluginOptionsEditorProps; From 16c4edcde6f9eabe113dba0a669ca3a73e7a49ea Mon Sep 17 00:00:00 2001 From: Kishan B Date: Mon, 21 Mar 2022 22:29:05 +0530 Subject: [PATCH 05/10] Retrieve WithEvent from settings and not from apiInput because it is only used in the Execute method --- pkg/redshift/api/api.go | 4 +--- pkg/redshift/api/api_test.go | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/pkg/redshift/api/api.go b/pkg/redshift/api/api.go index 29baebeb..19976e80 100644 --- a/pkg/redshift/api/api.go +++ b/pkg/redshift/api/api.go @@ -66,14 +66,12 @@ type apiInput struct { Database *string DbUser *string SecretARN *string - WithEvent *bool } func (c *API) apiInput() apiInput { res := apiInput{ ClusterIdentifier: aws.String(c.settings.ClusterIdentifier), Database: aws.String(c.settings.Database), - WithEvent: aws.Bool(c.settings.WithEvent), } if c.settings.UseManagedSecret { res.SecretARN = aws.String(c.settings.ManagedSecret.ARN) @@ -91,7 +89,7 @@ func (c *API) Execute(ctx context.Context, input *api.ExecuteQueryInput) (*api.E DbUser: commonInput.DbUser, SecretArn: commonInput.SecretARN, Sql: aws.String(input.Query), - WithEvent: commonInput.WithEvent, + WithEvent: aws.Bool(c.settings.WithEvent), } output, err := c.DataClient.ExecuteStatementWithContext(ctx, redshiftInput) diff --git a/pkg/redshift/api/api_test.go b/pkg/redshift/api/api_test.go index 802e61c5..2e91ce2f 100644 --- a/pkg/redshift/api/api_test.go +++ b/pkg/redshift/api/api_test.go @@ -33,7 +33,6 @@ func Test_apiInput(t *testing.T) { ClusterIdentifier: aws.String("cluster"), Database: aws.String("db"), DbUser: aws.String("user"), - WithEvent: aws.Bool(false), }, }, { @@ -50,22 +49,6 @@ func Test_apiInput(t *testing.T) { ClusterIdentifier: aws.String("cluster"), Database: aws.String("db"), SecretARN: aws.String("arn:..."), - WithEvent: aws.Bool(false), - }, - }, - { - "with event", - &models.RedshiftDataSourceSettings{ - WithEvent: true, - ClusterIdentifier: "cluster", - Database: "db", - DBUser: "user", - }, - apiInput{ - WithEvent: aws.Bool(true), - ClusterIdentifier: aws.String("cluster"), - Database: aws.String("db"), - DbUser: aws.String("user"), }, }, } From 9c418389fb08b42a40ceca1a40adfa280ae98638 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Mon, 21 Mar 2022 22:32:05 +0530 Subject: [PATCH 06/10] Vertically align the WithEvent switch --- src/ConfigEditor/ConfigEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConfigEditor/ConfigEditor.tsx b/src/ConfigEditor/ConfigEditor.tsx index 7ca8b9a6..0b944d53 100644 --- a/src/ConfigEditor/ConfigEditor.tsx +++ b/src/ConfigEditor/ConfigEditor.tsx @@ -210,7 +210,7 @@ export function ConfigEditor(props: Props) { label={selectors.components.ConfigEditor.Database.input} data-testid={selectors.components.ConfigEditor.Database.testID} /> - + From c75745a5fde800785384915ea72dced74d33338c Mon Sep 17 00:00:00 2001 From: Kishan B Date: Tue, 22 Mar 2022 06:38:21 +0530 Subject: [PATCH 07/10] Add unit test for the with event switch --- src/ConfigEditor/ConfigEditor.test.tsx | 15 +++++++++++++++ src/ConfigEditor/ConfigEditor.tsx | 3 ++- src/selectors.ts | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ConfigEditor/ConfigEditor.test.tsx b/src/ConfigEditor/ConfigEditor.test.tsx index b7a14746..5daee9b8 100644 --- a/src/ConfigEditor/ConfigEditor.test.tsx +++ b/src/ConfigEditor/ConfigEditor.test.tsx @@ -88,6 +88,21 @@ describe('ConfigEditor', () => { }); }); + it('should enable WithEvent when it is toggled on', async () => { + const onChange = jest.fn(); + render(); + + const withEventField = screen.getByTestId('data-testid withEvent'); + expect(withEventField).toBeInTheDocument(); + fireEvent.change(withEventField, { target: { checked: true } }); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith({ + ...props.options, + jsonData: { ...props.options.jsonData, withEvent: true }, + }); + }); + it('should populate the `url` prop when clusterIdentifier is selected', async () => { const onChange = jest.fn(); render( diff --git a/src/ConfigEditor/ConfigEditor.tsx b/src/ConfigEditor/ConfigEditor.tsx index 0b944d53..324d7012 100644 --- a/src/ConfigEditor/ConfigEditor.tsx +++ b/src/ConfigEditor/ConfigEditor.tsx @@ -210,7 +210,7 @@ export function ConfigEditor(props: Props) { label={selectors.components.ConfigEditor.Database.input} data-testid={selectors.components.ConfigEditor.Database.testID} /> - + @@ -223,6 +223,7 @@ export function ConfigEditor(props: Props) { }) } css={undefined} + data-testid={selectors.components.ConfigEditor.WithEvent.testID} /> diff --git a/src/selectors.ts b/src/selectors.ts index 92a29f57..c5b725d2 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -46,6 +46,10 @@ export const Components = { input: 'Column', testID: 'data-testid column', }, + WithEvent: { + input: 'Send events to Amazon EventBridge', + testID: 'data-testid withEvent', + }, }, QueryEditor: { CodeEditor: { From 3c7eb1aa0be588281f6d51a3728fa3744cbf7991 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Thu, 24 Mar 2022 13:36:45 +0530 Subject: [PATCH 08/10] Fix failing with event test by firing click event instead of onchange event --- src/ConfigEditor/ConfigEditor.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ConfigEditor/ConfigEditor.test.tsx b/src/ConfigEditor/ConfigEditor.test.tsx index 5daee9b8..441d915e 100644 --- a/src/ConfigEditor/ConfigEditor.test.tsx +++ b/src/ConfigEditor/ConfigEditor.test.tsx @@ -91,10 +91,10 @@ describe('ConfigEditor', () => { it('should enable WithEvent when it is toggled on', async () => { const onChange = jest.fn(); render(); - const withEventField = screen.getByTestId('data-testid withEvent'); expect(withEventField).toBeInTheDocument(); - fireEvent.change(withEventField, { target: { checked: true } }); + + fireEvent.click(withEventField); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith({ From 0bebb56160720c5695f1c1edcbfdcdebbcafc9bb Mon Sep 17 00:00:00 2001 From: Kishan B Date: Thu, 24 Mar 2022 13:37:13 +0530 Subject: [PATCH 09/10] Fix lint issue by running prettier --- src/ConfigEditor/ConfigEditor.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ConfigEditor/ConfigEditor.tsx b/src/ConfigEditor/ConfigEditor.tsx index 324d7012..4e9ef627 100644 --- a/src/ConfigEditor/ConfigEditor.tsx +++ b/src/ConfigEditor/ConfigEditor.tsx @@ -210,7 +210,12 @@ export function ConfigEditor(props: Props) { label={selectors.components.ConfigEditor.Database.input} data-testid={selectors.components.ConfigEditor.Database.testID} /> - + From 9d88b8376337484158da524746e38d0993098812 Mon Sep 17 00:00:00 2001 From: Kishan B Date: Thu, 24 Mar 2022 13:38:40 +0530 Subject: [PATCH 10/10] Use declared constant for test identifier in with event test --- src/ConfigEditor/ConfigEditor.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConfigEditor/ConfigEditor.test.tsx b/src/ConfigEditor/ConfigEditor.test.tsx index 441d915e..c2daa237 100644 --- a/src/ConfigEditor/ConfigEditor.test.tsx +++ b/src/ConfigEditor/ConfigEditor.test.tsx @@ -91,7 +91,7 @@ describe('ConfigEditor', () => { it('should enable WithEvent when it is toggled on', async () => { const onChange = jest.fn(); render(); - const withEventField = screen.getByTestId('data-testid withEvent'); + const withEventField = screen.getByTestId(selectors.components.ConfigEditor.WithEvent.testID); expect(withEventField).toBeInTheDocument(); fireEvent.click(withEventField);