Skip to content

Commit

Permalink
Add ConfigEditor util components (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
andresmgot authored Nov 15, 2021
1 parent 8167026 commit 0e03616
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { ConnectionConfig, ConnectionConfigProps } from './ConnectionConfig';
export { ConfigSelect, InlineInput } from './sql/ConfigEditor';
export { ResourceSelector, ResourceSelectorProps } from './sql/ResourceSelector';
export * from './types';
export * from './regions';
Expand Down
55 changes: 55 additions & 0 deletions src/sql/ConfigEditor/ConfigSelect.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ConfigSelect, ConfigSelectProps } from './ConfigSelect';
import { mockDatasourceOptions } from './__mocks__/datasource';
import { select } from 'react-select-event';

const props: ConfigSelectProps = {
...mockDatasourceOptions,
jsonDataPath: 'foo',
fetch: jest.fn(),
saveOptions: jest.fn(),
};

describe('SQLTextInput', () => {
it('should update jsonData', async () => {
const fetch = jest.fn().mockResolvedValue(['bar']);
const onOptionsChange = jest.fn();
const label = 'foo-id';
render(<ConfigSelect {...props} label={label} fetch={fetch} onOptionsChange={onOptionsChange} />);

const selectEl = screen.getByLabelText(label);
expect(selectEl).toBeInTheDocument();
await select(selectEl, 'bar', { container: document.body });
expect(fetch).toHaveBeenCalled();
expect(onOptionsChange).toHaveBeenCalledWith({
...props.options,
jsonData: {
...props.options.jsonData,
foo: 'bar',
},
});
});

it('should call deep nested jsonData value', async () => {
const onOptionsChange = jest.fn();
const fetch = jest.fn().mockResolvedValue(['foobar']);
const label = 'foo-id';
render(
<ConfigSelect {...props} label={label} jsonDataPath="foo.bar" fetch={fetch} onOptionsChange={onOptionsChange} />
);
const selectEl = screen.getByLabelText(label);
expect(selectEl).toBeInTheDocument();
await select(selectEl, 'foobar', { container: document.body });
expect(fetch).toHaveBeenCalled();
expect(onOptionsChange).toHaveBeenCalledWith({
...props.options,
jsonData: {
...props.options.jsonData,
foo: {
bar: 'foobar',
},
},
});
});
});
61 changes: 61 additions & 0 deletions src/sql/ConfigEditor/ConfigSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data';
import { AwsAuthDataSourceJsonData, AwsAuthDataSourceSecureJsonData } from '../../types';
import { ResourceSelector } from '../ResourceSelector';
import { set, get } from 'lodash';

export interface ConfigSelectProps
extends DataSourcePluginOptionsEditorProps<AwsAuthDataSourceJsonData, AwsAuthDataSourceSecureJsonData> {
jsonDataPath: string;
fetch: () => Promise<Array<string | SelectableValue<string>>>;
dependencies?: string[];
label?: string;
'data-testid'?: string;
hidden?: boolean;
disabled?: boolean;
jsonDataPathLabel?: string;
saveOptions: () => Promise<void>;
}

export function ConfigSelect(props: ConfigSelectProps) {
const { jsonData } = props.options;
const commonProps = {
title: jsonData.defaultRegion ? '' : 'select a default region',
disabled: !jsonData.defaultRegion,
labelWidth: 28,
className: 'width-30',
};
const onChange = (e: SelectableValue<string> | null) => {
const newOptions = {
...props.options,
};
set(newOptions.jsonData, props.jsonDataPath, e ? e.value || '' : e);
if (props.jsonDataPathLabel) {
set(newOptions.jsonData, props.jsonDataPathLabel, e ? e.label || '' : e);
}
props.onOptionsChange(newOptions);
};
// Any change in the AWS connection details will affect selectors
const dependencies: string[] = [
props.options.jsonData.assumeRoleArn,
props.options.jsonData.authType,
props.options.jsonData.defaultRegion,
props.options.jsonData.endpoint,
props.options.jsonData.externalId,
props.options.jsonData.profile,
].concat(props.dependencies);
return (
<ResourceSelector
label={props.label}
data-testid={props['data-testid']}
onChange={onChange}
fetch={props.fetch}
value={get(props.options.jsonData, props.jsonDataPath)}
saveOptions={props.saveOptions}
dependencies={dependencies}
hidden={props.hidden}
disabled={props.disabled}
{...commonProps}
/>
);
}
46 changes: 46 additions & 0 deletions src/sql/ConfigEditor/InlineInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { InlineInputProps, InlineInput } from './InlineInput';
import { mockDatasourceOptions } from './__mocks__/datasource';

const props: InlineInputProps = {
...mockDatasourceOptions,
jsonDataPath: 'foo',
};

describe('InlineInput', () => {
it('should show jsonData value', () => {
render(<InlineInput {...props} options={{ ...props.options, jsonData: { foo: 'bar' } }} />);
expect(screen.queryByDisplayValue('bar')).toBeInTheDocument();
});

it('should update jsonData', () => {
const testID = 'foo-id';
const onOptionsChange = jest.fn();
render(<InlineInput {...props} data-testid={testID} onOptionsChange={onOptionsChange} />);
fireEvent.change(screen.getByTestId(testID), { target: { value: 'bar' } });
expect(onOptionsChange).toHaveBeenCalledWith({
...props.options,
jsonData: {
...props.options.jsonData,
foo: 'bar',
},
});
});

it('should update deep nested jsonData value', () => {
const onOptionsChange = jest.fn();
const testID = 'foo-id';
render(<InlineInput {...props} data-testid={testID} onOptionsChange={onOptionsChange} jsonDataPath="foo.bar" />);
fireEvent.change(screen.getByTestId(testID), { target: { value: 'foobar' } });
expect(onOptionsChange).toHaveBeenCalledWith({
...props.options,
jsonData: {
...props.options.jsonData,
foo: {
bar: 'foobar',
},
},
});
});
});
45 changes: 45 additions & 0 deletions src/sql/ConfigEditor/InlineInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { AwsAuthDataSourceSecureJsonData } from '../../types';
import { InlineField, Input } from '@grafana/ui';
import { FormEvent } from 'react-dom/node_modules/@types/react';
import { get, set } from 'lodash';

export interface InlineInputProps extends DataSourcePluginOptionsEditorProps<{}, AwsAuthDataSourceSecureJsonData> {
jsonDataPath: string;
label?: string;
tooltip?: string;
placeholder?: string;
'data-testid'?: string;
hidden?: boolean;
disabled?: boolean;
}

export function InlineInput(props: InlineInputProps) {
const onChange = (e: FormEvent<HTMLInputElement>) => {
const newOptions = {
...props.options,
};
set(newOptions.jsonData, props.jsonDataPath, e.currentTarget.value || '');
props.onOptionsChange(newOptions);
};

return (
<InlineField
label={props.label}
labelWidth={28}
tooltip={props.tooltip}
hidden={props.hidden}
disabled={props.disabled}
>
<Input
data-testid={props['data-testid']}
className="width-30"
value={get(props.options.jsonData, props.jsonDataPath, '')}
onChange={onChange}
placeholder={props.placeholder}
disabled={props.disabled}
/>
</InlineField>
);
}
33 changes: 33 additions & 0 deletions src/sql/ConfigEditor/__mocks__/datasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { AwsAuthDataSourceJsonData, AwsAuthDataSourceSecureJsonData } from 'types';

export const mockDatasourceOptions: DataSourcePluginOptionsEditorProps<
AwsAuthDataSourceJsonData,
AwsAuthDataSourceSecureJsonData
> = {
options: {
id: 1,
uid: 'redshift-id',
orgId: 1,
name: 'Redshift',
typeLogoUrl: '',
type: '',
typeName: '',
access: '',
url: '',
password: '',
user: '',
basicAuth: false,
basicAuthPassword: '',
basicAuthUser: '',
database: '',
isDefault: false,
jsonData: {
defaultRegion: 'us-east-2',
},
secureJsonFields: {},
readOnly: false,
withCredentials: false,
},
onOptionsChange: jest.fn(),
};
2 changes: 2 additions & 0 deletions src/sql/ConfigEditor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ConfigSelect, ConfigSelectProps } from './ConfigSelect';
export { InlineInput, InlineInputProps } from './InlineInput';
3 changes: 2 additions & 1 deletion src/sql/ResourceSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type ResourceSelectorProps = {
tooltip?: string;
label?: string;
'data-testid'?: string;
hidden?: boolean;
// Options only needed for QueryEditor
default?: string;
// Options only needed for the ConfigEditor
Expand Down Expand Up @@ -99,7 +100,7 @@ export function ResourceSelector(props: ResourceSelectorProps) {
};

return (
<InlineField label={props.label} labelWidth={props.labelWidth} tooltip={props.tooltip}>
<InlineField label={props.label} labelWidth={props.labelWidth} tooltip={props.tooltip} hidden={props.hidden}>
<div data-testid={props['data-testid']} title={props.title}>
<Select
aria-label={props.label}
Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataSourceJsonData } from '@grafana/data';
import { DataSourceJsonData, DataSourceSettings } from '@grafana/data';

export enum AwsAuthType {
Keys = 'keys',
Expand All @@ -25,3 +25,5 @@ export interface AwsAuthDataSourceSecureJsonData {
secretKey?: string;
sessionToken?: string;
}

export type AwsAuthDataSourceSettings = DataSourceSettings<AwsAuthDataSourceJsonData, AwsAuthDataSourceSecureJsonData>;
13 changes: 4 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3753,15 +3753,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181:
version "1.0.30001204"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz#256c85709a348ec4d175e847a3b515c66e79f2aa"
integrity sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==

caniuse-lite@^1.0.30001125:
version "1.0.30001276"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001276.tgz#7049685eb972eb09c0ecbb57227b489d76244fb1"
integrity sha512-psUNoaG1ilknZPxi8HuhQWobuhLqtYSRUxplfVkEJdgZNB9TETVYGSBtv4YyfAdGvE6gn2eb0ztiXqHoWJcGnw==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181:
version "1.0.30001279"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz"
integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==

capture-exit@^2.0.0:
version "2.0.0"
Expand Down

0 comments on commit 0e03616

Please sign in to comment.