Skip to content

Commit

Permalink
Variables: Add new allowCustomValue flag to MultiVariables (#956)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergej-Vlasov <sergej.s.vlasov@gmail.com>
  • Loading branch information
mdvictor and Sergej-Vlasov authored Nov 12, 2024
1 parent afa4af9 commit b8bf1ee
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 8 deletions.
4 changes: 2 additions & 2 deletions packages/scenes/src/variables/adhoc/AdHocFilterRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function AdHocFilterRenderer({ filter, model }: Props) {
const valueSelect = (
<Select
virtualized
allowCustomValue
allowCustomValue={model.state.allowCustomValue ?? true}
isValidNewOption={(inputValue) => inputValue.trim().length > 0}
allowCreateWhileLoading
formatCreateLabel={(inputValue) => `Use custom value: ${inputValue}`}
Expand Down Expand Up @@ -171,7 +171,7 @@ export function AdHocFilterRenderer({ filter, model }: Props) {
disabled={model.state.readOnly}
className={cx(styles.key, isKeysOpen ? styles.widthWhenOpen : undefined)}
width="auto"
allowCustomValue={true}
allowCustomValue={model.state.allowCustomValue ?? true}
value={keyValue}
placeholder={'Select label'}
options={handleOptionGroups(keys)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const AdHocCombobox = forwardRef(function AdHocCombobox(
// control multi values with local state in order to commit all values at once and avoid _wip reset mid creation
const [filterMultiValues, setFilterMultiValues] = useState<Array<SelectableValue<string>>>([]);
const [_, setForceRefresh] = useState({});
const allowCustomValue = model.state.allowCustomValue ?? true;

const multiValuePillWrapperRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -206,7 +207,7 @@ export const AdHocCombobox = forwardRef(function AdHocCombobox(
const filteredDropDownItems = flattenOptionGroups(handleOptionGroups(optionsSearcher(inputValue, filterInputType)));

// adding custom option this way so that virtualiser is aware of it and can scroll to
if (filterInputType !== 'operator' && inputValue) {
if (allowCustomValue && filterInputType !== 'operator' && inputValue) {
filteredDropDownItems.push({
value: inputValue.trim(),
label: inputValue.trim(),
Expand Down Expand Up @@ -345,6 +346,7 @@ export const AdHocCombobox = forwardRef(function AdHocCombobox(
(event: React.KeyboardEvent, multiValueEdit?: boolean) => {
if (event.key === 'Enter' && activeIndex != null) {
// safeguard for non existing items
// note: custom item is added to filteredDropDownItems if allowed
if (!filteredDropDownItems[activeIndex]) {
return;
}
Expand Down Expand Up @@ -587,7 +589,8 @@ export const AdHocCombobox = forwardRef(function AdHocCombobox(
<LoadingOptionsPlaceholder />
) : optionsError ? (
<OptionsErrorPlaceholder handleFetchOptions={() => handleFetchOptions(filterInputType)} />
) : !filteredDropDownItems.length && (filterInputType === 'operator' || !inputValue) ? (
) : !filteredDropDownItems.length &&
(!allowCustomValue || filterInputType === 'operator' || !inputValue) ? (
<NoOptionsPlaceholder />
) : (
rowVirtualizer.getVirtualItems().map((virtualItem) => {
Expand Down
5 changes: 5 additions & 0 deletions packages/scenes/src/variables/adhoc/AdHocFiltersVariable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ export interface AdHocFiltersVariableState extends SceneVariableState {
*/
useQueriesAsFilterForOptions?: boolean;

/**
* Flag that decides whether custom values can be added to the filter
*/
allowCustomValue?: boolean;

/**
* @internal state of the new filter being added
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('VariableValueSelect', () => {
name: 'test',
query: 'A,B,C',
isMulti: true,
allowCustomValue: true,
value: [],
text: '',
options: [
Expand Down Expand Up @@ -129,4 +130,41 @@ describe('VariableValueSelect', () => {
const options = screen.getAllByRole('option');
expect(options).toHaveLength(3);
});

it('should render custom values in VariableValueSelect component', async () => {
render(<VariableValueSelect model={model} />);
const variableValueSelectElement = screen.getByTestId(
selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(`${model.state.value}`)
);
const inputElement = variableValueSelectElement.querySelector('input');
expect(inputElement).toBeInTheDocument();
if (!inputElement) {
return;
}

//type custom value in input
await userEvent.type(inputElement, 'custom value');
let options = screen.getAllByRole('option');
//expect custom value to be the only value added to options
expect(options).toHaveLength(1);
});

it('should not render custom values when allowCustomValue is false in VariableValueSelect component', async () => {
model.setState({ allowCustomValue: false });

render(<VariableValueSelect model={model} />);
const variableValueSelectElement = screen.getByTestId(
selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(`${model.state.value}`)
);
const inputElement = variableValueSelectElement.querySelector('input');
expect(inputElement).toBeInTheDocument();
if (!inputElement) {
return;
}

//expect no options now since we are typing a value that isn't in the list of options and also we can't add custom values
await userEvent.type(inputElement, 'custom value');
const options = screen.queryAllByRole('option');
expect(options).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function toSelectableValue<T>(value: T, label?: string): SelectableValue<
}

export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVariable>) {
const { value, text, key, options, includeAll, isReadOnly } = model.useState();
const { value, text, key, options, includeAll, isReadOnly, allowCustomValue = true } = model.useState();
const [inputValue, setInputValue] = useState('');
const [hasCustomValue, setHasCustomValue] = useState(false);
const selectValue = toSelectableValue(value, String(text));
Expand Down Expand Up @@ -80,7 +80,7 @@ export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVar
disabled={isReadOnly}
value={selectValue}
inputValue={inputValue}
allowCustomValue
allowCustomValue={allowCustomValue}
virtualized
filterOption={filterNoOp}
tabSelectsValue={false}
Expand All @@ -101,7 +101,7 @@ export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVar
}

export function VariableValueSelectMulti({ model }: SceneComponentProps<MultiValueVariable>) {
const { value, options, key, maxVisibleValues, noValueOnClear, includeAll, isReadOnly } = model.useState();
const { value, options, key, maxVisibleValues, noValueOnClear, includeAll, isReadOnly, allowCustomValue = true } = model.useState();
const arrayValue = useMemo(() => (isArray(value) ? value : [value]), [value]);
// To not trigger queries on every selection we store this state locally here and only update the variable onBlur
const [uncommittedValue, setUncommittedValue] = useState(arrayValue);
Expand Down Expand Up @@ -146,7 +146,7 @@ export function VariableValueSelectMulti({ model }: SceneComponentProps<MultiVal
maxVisibleValues={maxVisibleValues ?? 5}
tabSelectsValue={false}
virtualized
allowCustomValue
allowCustomValue={allowCustomValue}
//@ts-ignore
toggleAllOptions={{
enabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface MultiValueVariableState extends SceneVariableState {
value: VariableValue; // old current.text
text: VariableValue; // old current.value
options: VariableValueOption[];
allowCustomValue?: boolean;
isMulti?: boolean;
includeAll?: boolean;
defaultToAll?: boolean;
Expand Down

0 comments on commit b8bf1ee

Please sign in to comment.