Skip to content

Commit

Permalink
Add Local mode to access local instance (#51)
Browse files Browse the repository at this point in the history
* Refactor APIs

* Update tests

* Add local request mode

* Update tests

* Update Select to Radio

* Fix tests

---------

Co-authored-by: Mikhail <[email protected]>
  • Loading branch information
asimonok and mikhail-vl authored Sep 10, 2023
1 parent 864749c commit b35fc76
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 35 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@types/node": "^18.16.19",
"@types/webpack-env": "^1.18.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@volkovlabs/jest-selectors": "^1.2.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"eslint-plugin-deprecation": "^1.5.0",
Expand Down
41 changes: 41 additions & 0 deletions src/__mocks__/@grafana/ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';

const actual = jest.requireActual('@grafana/ui');

/**
* Mock Select component
*/
const Select = jest.fn(({ options, onChange, value, ...restProps }) => (
<select
onChange={(event: any) => {
if (onChange) {
onChange(
options.find((option: any) => {
/**
* Jest convert number to string, so just using not strict comparison
*/
// eslint-disable-next-line eqeqeq
return option.value == event.target.value;
})
);
}
}}
/**
* Fix jest warnings because null value.
* For Select component in @grafana/ui should be used null to reset value.
*/
value={value === null ? '' : value}
{...restProps}
>
{options.map(({ label, value }: any) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
));

module.exports = {
...actual,
Select,
};
55 changes: 50 additions & 5 deletions src/components/ConfigEditor/ConfigEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { DataSourceSettings } from '@grafana/data';
import { act, fireEvent, render, screen } from '@testing-library/react';
import { TestIds } from '../../constants';
import { getJestSelectors } from '@volkovlabs/jest-selectors';
import { RequestMode, TestIds } from '../../constants';
import { DataSourceOptions } from '../../types';
import { ConfigEditor } from './ConfigEditor';

Expand Down Expand Up @@ -42,6 +43,7 @@ const getOptions = ({
...overrideOptions,
jsonData: {
url: '',
requestMode: RequestMode.REMOTE,
...jsonData,
},
secureJsonData: {
Expand All @@ -56,10 +58,53 @@ const getOptions = ({
describe('ConfigEditor', () => {
const onChange = jest.fn();

/**
* Selectors
*/
const getSelectors = getJestSelectors(TestIds.configEditor);
const selectors = getSelectors(screen);

beforeEach(() => {
onChange.mockReset();
});

describe('Request Mode', () => {
it('Should hide url and token if local mode enabled', async () => {
const options = getOptions({ jsonData: { url: 'http://localhost:3000', requestMode: RequestMode.REMOTE } });

const { rerender } = render(<ConfigEditor options={options} onOptionsChange={onChange} />);

expect(screen.getByLabelText(TestIds.configEditor.fieldRequestModelOption(RequestMode.REMOTE))).toBeChecked();

const newValue = RequestMode.LOCAL;
fireEvent.click(screen.getByLabelText(TestIds.configEditor.fieldRequestModelOption(newValue)));

expect(onChange).toHaveBeenCalledWith({
...options,
jsonData: {
...options.jsonData,
requestMode: newValue,
},
});

/**
* Render with local mode
*/
rerender(
<ConfigEditor
options={getOptions({ jsonData: { url: 'http://localhost:3000', requestMode: RequestMode.LOCAL } })}
onOptionsChange={onChange}
/>
);

/**
* Url and Token should be hidden
*/
expect(selectors.fieldUrl(true)).not.toBeInTheDocument();
expect(selectors.fieldToken(true)).not.toBeInTheDocument();
});
});

/**
* URL
*/
Expand All @@ -69,7 +114,7 @@ describe('ConfigEditor', () => {

render(<ConfigEditor options={options} onOptionsChange={onChange} />);

const fieldUrl = screen.getByTestId(TestIds.configEditor.fieldUrl);
const fieldUrl = selectors.fieldUrl();

expect(fieldUrl).toHaveValue(options.jsonData.url);

Expand All @@ -96,7 +141,7 @@ describe('ConfigEditor', () => {

render(<ConfigEditor options={options} onOptionsChange={onChange} />);

const fieldPassword = screen.getByTestId(TestIds.configEditor.fieldPassword);
const fieldPassword = selectors.fieldToken();

expect(fieldPassword).toHaveValue(options.secureJsonData.token);

Expand Down Expand Up @@ -127,7 +172,7 @@ describe('ConfigEditor', () => {
/>
);

const fieldPassword = screen.getByTestId(TestIds.configEditor.fieldPassword);
const fieldPassword = selectors.fieldToken();

expect(fieldPassword).toHaveValue('');
});
Expand All @@ -148,7 +193,7 @@ describe('ConfigEditor', () => {
/>
);

const fieldPassword = screen.getByTestId(TestIds.configEditor.fieldPassword);
const fieldPassword = selectors.fieldToken();

expect(fieldPassword).toHaveProperty('placeholder', 'configured');
});
Expand Down
68 changes: 47 additions & 21 deletions src/components/ConfigEditor/ConfigEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ChangeEvent, useCallback } from 'react';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { FieldSet, InlineField, InlineFieldRow, Input } from '@grafana/ui';
import { TestIds } from '../../constants';
import { FieldSet, InlineField, InlineFieldRow, Input, RadioButtonGroup } from '@grafana/ui';
import { RequestMode, RequestModeOptions, TestIds } from '../../constants';
import { DataSourceOptions, SecureJsonData } from '../../types';

/**
Expand All @@ -13,6 +13,22 @@ interface Props extends DataSourcePluginOptionsEditorProps<DataSourceOptions, Se
* Config Editor
*/
export const ConfigEditor: React.FC<Props> = ({ onOptionsChange, options }) => {
/**
* Request Mode Change
*/
const onRequestModeChange = useCallback(
(value: RequestMode) => {
onOptionsChange({
...options,
jsonData: {
...options.jsonData,
requestMode: value,
},
});
},
[onOptionsChange, options]
);

/**
* API URL Change
*/
Expand Down Expand Up @@ -53,28 +69,38 @@ export const ConfigEditor: React.FC<Props> = ({ onOptionsChange, options }) => {
return (
<FieldSet>
<InlineFieldRow>
<InlineField label="Grafana URL" labelWidth={14} grow>
<Input
type="text"
placeholder="http://localhost:3000"
value={jsonData.url}
onChange={onUrlChange}
data-testid={TestIds.configEditor.fieldUrl}
/>
<InlineField label="Request Mode" labelWidth={14} grow data-testid={TestIds.configEditor.fieldRequestMode}>
<RadioButtonGroup value={jsonData.requestMode} options={RequestModeOptions} onChange={onRequestModeChange} />
</InlineField>
</InlineFieldRow>

<InlineFieldRow>
<InlineField label="Token" labelWidth={14} grow>
<Input
type="password"
placeholder={secureJsonFields?.token ? 'configured' : ''}
value={secureJsonData.token ?? ''}
onChange={onTokenChange}
data-testid={TestIds.configEditor.fieldPassword}
/>
</InlineField>
</InlineFieldRow>
{jsonData.requestMode !== RequestMode.LOCAL && (
<>
<InlineFieldRow>
<InlineField label="Grafana URL" labelWidth={14} grow>
<Input
type="text"
placeholder="http://localhost:3000"
value={jsonData.url}
onChange={onUrlChange}
data-testid={TestIds.configEditor.fieldUrl}
/>
</InlineField>
</InlineFieldRow>

<InlineFieldRow>
<InlineField label="Token" labelWidth={14} grow>
<Input
type="password"
placeholder={secureJsonFields?.token ? 'configured' : ''}
value={secureJsonData.token ?? ''}
onChange={onTokenChange}
data-testid={TestIds.configEditor.fieldToken}
/>
</InlineField>
</InlineFieldRow>
</>
)}
</FieldSet>
);
};
28 changes: 27 additions & 1 deletion src/constants/datasource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import { TestIds } from './tests';

/**
* Datasource test status
* Data Source test status
*/
export enum DataSourceTestStatus {
SUCCESS = 'success',
ERROR = 'error',
}

/**
* Request Mode
*/
export enum RequestMode {
LOCAL = 'local',
REMOTE = 'remote',
}

/**
* Request Mode Options
*/
export const RequestModeOptions = [
{
label: 'Local',
value: RequestMode.LOCAL,
ariaLabel: TestIds.configEditor.fieldRequestModelOption(RequestMode.LOCAL),
},
{
label: 'Remote',
value: RequestMode.REMOTE,
ariaLabel: TestIds.configEditor.fieldRequestModelOption(RequestMode.REMOTE),
},
];
4 changes: 3 additions & 1 deletion src/constants/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
*/
export const TestIds = {
configEditor: {
fieldPassword: 'data-testid config-editor field-password',
fieldToken: 'data-testid config-editor field-token',
fieldUrl: 'data-testid config-editor field-url',
fieldRequestMode: 'data-testid config-editor field-request-mode',
fieldRequestModelOption: (name: string) => `data-testid config-editor field-request-mode-option-${name}`,
},
queryEditor: {
fieldAnnotationDashboardContainer: 'data-testid query-editor field-annotation-dashboard-container',
Expand Down
Loading

0 comments on commit b35fc76

Please sign in to comment.