Skip to content

Commit

Permalink
TextBoxVariable: Fixes and make it auto size (#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
torkelo authored Oct 6, 2023
1 parent 9445d6d commit b9215b5
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 47 deletions.
21 changes: 19 additions & 2 deletions packages/scenes/src/variables/TestScene.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import { SceneObjectBase } from '../core/SceneObjectBase';
import { SceneObjectState } from '../core/types';
import { SceneObject, SceneObjectState } from '../core/types';
import { VariableDependencyConfig } from './VariableDependencyConfig';

/**
* Used in a couple of unit tests
*/
export interface TestSceneState extends SceneObjectState {
nested?: TestScene;
nested?: SceneObject;
/** To test logic for inactive scene objects */
hidden?: SceneObject;
}

export class TestScene extends SceneObjectBase<TestSceneState> {}

interface TestSceneObjectState extends SceneObjectState {
title: string;
variableValueChanged: number;
}

export class TestObjectWithVariableDependency extends SceneObjectBase<TestSceneObjectState> {
protected _variableDependency = new VariableDependencyConfig(this, {
statePaths: ['title'],
onReferencedVariableValueChanged: () => {
this.setState({ variableValueChanged: this.state.variableValueChanged + 1 });
},
});
}
38 changes: 24 additions & 14 deletions packages/scenes/src/variables/components/VariableValueInput.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import React, { useState, useCallback } from 'react';
import React, { useCallback } from 'react';

import { Input } from '@grafana/ui';
import { AutoSizeInput } from '@grafana/ui';

import { SceneComponentProps } from '../../core/types';
import { TextBoxVariable } from '../variants/TextBoxVariable';
import { useDebounce } from 'react-use';

export function VariableValueInput({ model }: SceneComponentProps<TextBoxVariable>) {
const { value, key, loading } = model.useState();
const [textValue, setTextValue] = useState(value);
useDebounce(
() => {
model.setValue(textValue);

const onBlur = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
model.setValue(e.currentTarget.value);
},
250,
[textValue]
[model]
);

const onChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setTextValue(e.target.value);
const onKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
model.setValue(e.currentTarget.value);
}
},
[setTextValue]
[model]
);

return <Input id={key} placeholder="Enter value" value={textValue} loading={loading} onChange={onChange} />;
return (
<AutoSizeInput
id={key}
placeholder="Enter value"
minWidth={15}
defaultValue={value}
loading={loading}
onBlur={onBlur}
onKeyDown={onKeyDown}
/>
);
}
37 changes: 8 additions & 29 deletions packages/scenes/src/variables/sets/SceneVariableSet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { VariableRefresh } from '@grafana/data';

import { SceneFlexItem, SceneFlexLayout } from '../../components/layout/SceneFlexLayout';
import { SceneObjectBase } from '../../core/SceneObjectBase';
import { SceneObjectState, SceneObject, SceneComponentProps } from '../../core/types';
import { SceneObjectState, SceneComponentProps } from '../../core/types';
import { TestVariable } from '../variants/TestVariable';

import { SceneVariableSet } from './SceneVariableSet';
Expand All @@ -14,14 +14,7 @@ import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants';
import { sceneGraph } from '../../core/sceneGraph';
import { SceneTimeRange } from '../../core/SceneTimeRange';
import { LocalValueVariable } from '../variants/LocalValueVariable';

interface TestSceneState extends SceneObjectState {
nested?: SceneObject;
/** To test logic for inactive scene objects */
hidden?: SceneObject;
}

class TestScene extends SceneObjectBase<TestSceneState> {}
import { TestObjectWithVariableDependency, TestScene } from '../TestScene';

interface SceneTextItemState extends SceneObjectState {
text: string;
Expand Down Expand Up @@ -290,8 +283,8 @@ describe('SceneVariableList', () => {
describe('When variables have change when re-activated broadcast changes', () => {
it('Should notify only active objects of change', async () => {
const A = new TestVariable({ name: 'A', query: 'A.*', value: '', text: '', options: [], delayMs: 1 });
const nestedObj = new TestSceneObect({ title: '$A', variableValueChanged: 0 });
const inActiveSceneObject = new TestSceneObect({ title: '$A', variableValueChanged: 0 });
const nestedObj = new TestObjectWithVariableDependency({ title: '$A', variableValueChanged: 0 });
const inActiveSceneObject = new TestObjectWithVariableDependency({ title: '$A', variableValueChanged: 0 });

const scene = new TestScene({
$variables: new SceneVariableSet({ variables: [A] }),
Expand Down Expand Up @@ -323,7 +316,7 @@ describe('SceneVariableList', () => {
it('Should notify scene objects if deactivated during chained update', async () => {
const A = new TestVariable({ name: 'A', query: 'A.*', value: '', text: '', options: [], delayMs: 1 });
const B = new TestVariable({ name: 'B', query: 'A.$A.*', value: '', text: '', options: [], delayMs: 1 });
const nestedSceneObject = new TestSceneObect({ title: '$A', variableValueChanged: 0 });
const nestedSceneObject = new TestObjectWithVariableDependency({ title: '$A', variableValueChanged: 0 });

const scene = new TestScene({
$variables: new SceneVariableSet({ variables: [A, B] }),
Expand All @@ -349,7 +342,7 @@ describe('SceneVariableList', () => {
it('Should handle being deactivated right away', async () => {
const A = new TestVariable({ name: 'A', query: 'A.*', value: '', text: '', options: [], delayMs: 1 });
const B = new TestVariable({ name: 'B', query: 'A.$A.*', value: '', text: '', options: [], delayMs: 1 });
const sceneObject = new TestSceneObect({ title: '$A', variableValueChanged: 0 });
const sceneObject = new TestObjectWithVariableDependency({ title: '$A', variableValueChanged: 0 });

const scene = new TestScene({
$variables: new SceneVariableSet({ variables: [A, B] }),
Expand Down Expand Up @@ -377,7 +370,7 @@ describe('SceneVariableList', () => {
isMulti: true,
});

const nestedSceneObject = new TestSceneObect({ title: '$A', variableValueChanged: 0 });
const nestedSceneObject = new TestObjectWithVariableDependency({ title: '$A', variableValueChanged: 0 });

const scene = new TestScene({
$variables: new SceneVariableSet({ variables: [A] }),
Expand All @@ -401,7 +394,7 @@ describe('SceneVariableList', () => {
it('Should start update process', async () => {
const A = new TestVariable({ name: 'A', query: 'A.*', value: '', text: '', options: [] });
const B = new TestVariable({ name: 'B', query: 'A.*', value: '', text: '', options: [] });
const nestedObj = new TestSceneObect({ title: '$B', variableValueChanged: 0 });
const nestedObj = new TestObjectWithVariableDependency({ title: '$B', variableValueChanged: 0 });
const set = new SceneVariableSet({ variables: [A] });

const scene = new TestScene({
Expand Down Expand Up @@ -545,17 +538,3 @@ describe('SceneVariableList', () => {
});
});
});

interface TestSceneObjectState extends SceneObjectState {
title: string;
variableValueChanged: number;
}

export class TestSceneObect extends SceneObjectBase<TestSceneObjectState> {
protected _variableDependency = new VariableDependencyConfig(this, {
statePaths: ['title'],
onReferencedVariableValueChanged: () => {
this.setState({ variableValueChanged: this.state.variableValueChanged + 1 });
},
});
}
29 changes: 29 additions & 0 deletions packages/scenes/src/variables/variants/TextBoxVariable.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';

import { render, screen } from '@testing-library/react';

import { TextBoxVariable } from './TextBoxVariable';
import { SceneVariableSet } from '../sets/SceneVariableSet';
import { EmbeddedScene } from '../../components/EmbeddedScene';
import { VariableValueSelectors } from '../components/VariableValueSelectors';
import { TestObjectWithVariableDependency } from '../TestScene';

describe('TextBoxVariable', () => {
it('Should not cause variable change mounted', async () => {
const nestedObj = new TestObjectWithVariableDependency({ title: '$search', variableValueChanged: 0 });

const scene = new EmbeddedScene({
$variables: new SceneVariableSet({ variables: [new TextBoxVariable({ name: 'search' })] }),
controls: [new VariableValueSelectors({})],
body: nestedObj,
});

render(<scene.Component model={scene} />);

// There was a debounce before that fired some time after mount
await new Promise((r) => setTimeout(r, 300));

expect(screen.getByText('search')).toBeInTheDocument();
expect(nestedObj.state.variableValueChanged).toBe(0);
});
});
6 changes: 4 additions & 2 deletions packages/scenes/src/variables/variants/TextBoxVariable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ export class TextBoxVariable
}

public setValue(newValue: string) {
this.setState({ value: newValue });
this.publishEvent(new SceneVariableValueChangedEvent(this), true);
if (newValue !== this.state.value) {
this.setState({ value: newValue });
this.publishEvent(new SceneVariableValueChangedEvent(this), true);
}
}

private getKey(): string {
Expand Down

0 comments on commit b9215b5

Please sign in to comment.