Skip to content
Snippets Groups Projects
Unverified Commit 6c439596 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Add ability to choose between Dropdown/Search/Input for String parameters (#27610)

parent c3d1a35b
No related branches found
No related tags found
No related merge requests found
Showing
with 149 additions and 60 deletions
......@@ -11,30 +11,30 @@ import type {
} from "metabase-types/api";
import type { Field as FieldRef } from "metabase-types/types/Query";
import {
isDate,
isDateWithoutTime,
isTime,
isNumber,
isNumeric,
isAddress,
isBoolean,
isString,
isSummable,
isScope,
isCategory,
isAddress,
isCity,
isState,
isZipCode,
isCountry,
isComment,
isCoordinate,
isLocation,
isCountry,
isDate,
isDateWithoutTime,
isDescription,
isComment,
isDimension,
isEntityName,
isFK,
isLocation,
isMetric,
isNumber,
isNumeric,
isPK,
isFK,
isEntityName,
isScope,
isState,
isString,
isSummable,
isTime,
isZipCode,
} from "metabase-lib/types/utils/isa";
import { getFilterOperators } from "metabase-lib/operators/utils";
import { getFieldValues } from "metabase-lib/queries/utils/field";
......@@ -488,6 +488,10 @@ class FieldInner extends Base {
return this.dimension().foreign(foreignField.dimension());
}
isVirtual() {
return typeof this.id !== "number";
}
/**
* @private
* @param {number} id
......
......@@ -49,7 +49,3 @@ function getFieldIdentifier(field: Field): number | string {
return id || name;
}
export function isVirtualFieldId(id: Field["id"]) {
return typeof id !== "number";
}
......@@ -16,7 +16,6 @@ import Question from "metabase-lib/Question";
import NativeQuery from "metabase-lib/queries/NativeQuery";
import StructuredQuery from "metabase-lib/queries/StructuredQuery";
import Query from "metabase-lib/queries/Query";
import { UiParameter } from "metabase-lib/parameters/types";
export type NativeSavedCard = SavedCard<NativeDatasetQuery>;
export type NativeUnsavedCard = UnsavedCard<NativeDatasetQuery>;
......@@ -141,14 +140,3 @@ export function getComposedModel(
return question;
}
export const createMockUiParameter = (
opts?: Partial<UiParameter>,
): UiParameter => ({
id: "1",
name: "text",
type: "string/=",
slug: "text",
fields: [],
...opts,
});
......@@ -32,7 +32,7 @@ export const PARAMETER_OPERATOR_TYPES = {
{
type: "string/=",
operator: "=",
name: t`Dropdown`,
name: t`Is`,
description: t`Select one or more values from a list or search box.`,
},
{
......
......@@ -7,5 +7,8 @@ export const createMockUiParameter = (
slug: "slug",
name: "Name",
type: "string/=",
values_query_type: "list",
values_source_type: null,
values_source_config: {},
...opts,
});
import { isVirtualFieldId } from "metabase-lib/metadata/utils/fields";
import Field from "metabase-lib/metadata/Field";
import {
FieldFilterUiParameter,
UiParameter,
......@@ -10,7 +10,7 @@ export const hasFields = (
return (parameter as FieldFilterUiParameter).fields != null;
};
export const getFields = (parameter: UiParameter) => {
export const getFields = (parameter: UiParameter): Field[] => {
if (hasFields(parameter)) {
return parameter.fields;
} else {
......@@ -19,5 +19,5 @@ export const getFields = (parameter: UiParameter) => {
};
export const getNonVirtualFields = (parameter: UiParameter) => {
return getFields(parameter).filter(field => !isVirtualFieldId(field.id));
return getFields(parameter).filter(field => !field.isVirtual());
};
......@@ -17,7 +17,7 @@ export const isValidSourceConfig = (
export const getDefaultSourceConfig = (
sourceType: ValuesSourceType,
sourceValues?: string[],
) => {
): ValuesSourceConfig => {
switch (sourceType) {
case "static-list":
return { values: sourceValues };
......
......@@ -38,6 +38,9 @@ export function getTemplateTagParameter(tag: TemplateTag): ParameterWithTarget {
name: tag["display-name"],
slug: tag.name,
default: tag.default,
values_query_type: "list",
values_source_type: null,
values_source_config: {},
};
}
......
......@@ -122,6 +122,9 @@ describe("parameters/utils/cards", () => {
slug: "a",
target: ["variable", ["template-tag", "a"]],
type: "foo",
values_query_type: "list",
values_source_type: null,
values_source_config: {},
},
{
default: undefined,
......@@ -130,6 +133,9 @@ describe("parameters/utils/cards", () => {
slug: "b",
target: ["variable", ["template-tag", "b"]],
type: "string/=",
values_query_type: "list",
values_source_type: null,
values_source_config: {},
},
{
default: undefined,
......@@ -138,6 +144,9 @@ describe("parameters/utils/cards", () => {
slug: "c",
target: ["variable", ["template-tag", "c"]],
type: "number/=",
values_query_type: "list",
values_source_type: null,
values_source_config: {},
},
{
default: undefined,
......@@ -146,6 +155,9 @@ describe("parameters/utils/cards", () => {
slug: "d",
target: ["variable", ["template-tag", "d"]],
type: "date/single",
values_query_type: "list",
values_source_type: null,
values_source_config: {},
},
{
default: undefined,
......@@ -154,6 +166,9 @@ describe("parameters/utils/cards", () => {
slug: "e",
target: ["dimension", ["template-tag", "e"]],
type: "foo",
values_query_type: "list",
values_source_type: null,
values_source_config: {},
},
];
......
......@@ -5,5 +5,8 @@ export const createMockParameter = (opts?: Partial<Parameter>): Parameter => ({
name: "text",
type: "string/=",
slug: "text",
values_query_type: "none",
values_source_type: null,
values_source_config: {},
...opts,
});
......@@ -42,10 +42,13 @@ export interface Parameter {
filteringParameters?: ParameterId[];
isMultiSelect?: boolean;
value?: any;
values_source_type?: ValuesSourceType;
values_source_config?: ValuesSourceConfig;
values_query_type: ValuesQueryType;
values_source_type: ValuesSourceType;
values_source_config: ValuesSourceConfig;
}
export type ValuesQueryType = "list" | "search" | "none";
export type ValuesSourceType = null | "card" | "static-list";
export interface ValuesSourceConfig {
......
......@@ -194,6 +194,19 @@ export const setParameterIsMultiSelect = createThunkAction(
},
);
export const SET_PARAMETER_QUERY_TYPE =
"metabase/dashboard/SET_PARAMETER_QUERY_TYPE";
export const setParameterQueryType = createThunkAction(
SET_PARAMETER_QUERY_TYPE,
(parameterId, queryType) => (dispatch, getState) => {
updateParameter(dispatch, getState, parameterId, parameter => ({
...parameter,
values_query_type: queryType,
}));
return { id: parameterId, queryType };
},
);
export const SET_PARAMETER_SOURCE_TYPE =
"metabase/dashboard/SET_PARAMETER_SOURCE_TYPE";
export const setParameterSourceType = createThunkAction(
......
......@@ -27,6 +27,7 @@ DashboardSidebars.propTypes = {
setParameterName: PropTypes.func.isRequired,
setParameterDefaultValue: PropTypes.func.isRequired,
setParameterIsMultiSelect: PropTypes.func.isRequired,
setParameterQueryType: PropTypes.func.isRequired,
setParameterSourceType: PropTypes.func.isRequired,
setParameterSourceConfig: PropTypes.func.isRequired,
setParameterFilteringParameters: PropTypes.func.isRequired,
......@@ -59,6 +60,7 @@ export function DashboardSidebars({
setParameterName,
setParameterDefaultValue,
setParameterIsMultiSelect,
setParameterQueryType,
setParameterSourceType,
setParameterSourceConfig,
setParameterFilteringParameters,
......@@ -124,6 +126,7 @@ export function DashboardSidebars({
onChangeName={setParameterName}
onChangeDefaultValue={setParameterDefaultValue}
onChangeIsMultiSelect={setParameterIsMultiSelect}
onChangeQueryType={setParameterQueryType}
onChangeSourceType={setParameterSourceType}
onChangeSourceConfig={setParameterSourceConfig}
onChangeFilteringParameters={setParameterFilteringParameters}
......
......@@ -2,14 +2,18 @@ import React, { ChangeEvent, useCallback } from "react";
import { t } from "ttag";
import InputBlurChange from "metabase/components/InputBlurChange";
import Radio from "metabase/core/components/Radio";
import { ValuesSourceConfig, ValuesSourceType } from "metabase-types/api";
import {
ValuesQueryType,
ValuesSourceConfig,
ValuesSourceType,
} from "metabase-types/api";
import { UiParameter } from "metabase-lib/parameters/types";
import { getIsMultiSelect } from "../../utils/dashboards";
import {
canUseCustomSource,
isSingleOrMultiSelectable,
} from "../../utils/parameter-type";
import ParameterSourceSettings from "../ParameterSourceSettings";
import ValuesSourceSettings from "../ValuesSourceSettings";
import {
SettingLabel,
SettingRemoveButton,
......@@ -28,6 +32,7 @@ export interface ParameterSettingsProps {
onChangeName: (name: string) => void;
onChangeDefaultValue: (value: unknown) => void;
onChangeIsMultiSelect: (isMultiSelect: boolean) => void;
onChangeQueryType: (queryType: ValuesQueryType) => void;
onChangeSourceType: (sourceType: ValuesSourceType) => void;
onChangeSourceConfig: (sourceOptions: ValuesSourceConfig) => void;
onRemoveParameter: () => void;
......@@ -38,6 +43,7 @@ const ParameterSettings = ({
onChangeName,
onChangeDefaultValue,
onChangeIsMultiSelect,
onChangeQueryType,
onChangeSourceType,
onChangeSourceConfig,
onRemoveParameter,
......@@ -61,8 +67,9 @@ const ParameterSettings = ({
{canUseCustomSource(parameter) && (
<SettingSection>
<SettingLabel>{t`How should users filter on this column?`}</SettingLabel>
<ParameterSourceSettings
<ValuesSourceSettings
parameter={parameter}
onChangeQueryType={onChangeQueryType}
onChangeSourceType={onChangeSourceType}
onChangeSourceConfig={onChangeSourceConfig}
/>
......
......@@ -4,6 +4,7 @@ import Radio from "metabase/core/components/Radio";
import Sidebar from "metabase/dashboard/components/Sidebar";
import {
ParameterId,
ValuesQueryType,
ValuesSourceConfig,
ValuesSourceType,
} from "metabase-types/api";
......@@ -22,6 +23,10 @@ export interface ParameterSidebarProps {
parameterId: ParameterId,
isMultiSelect: boolean,
) => void;
onChangeQueryType: (
parameterId: ParameterId,
sourceType: ValuesQueryType,
) => void;
onChangeSourceType: (
parameterId: ParameterId,
sourceType: ValuesSourceType,
......@@ -45,6 +50,7 @@ const ParameterSidebar = ({
onChangeName,
onChangeDefaultValue,
onChangeIsMultiSelect,
onChangeQueryType,
onChangeSourceType,
onChangeSourceConfig,
onChangeFilteringParameters,
......@@ -77,6 +83,13 @@ const ParameterSidebar = ({
[parameterId, onChangeIsMultiSelect],
);
const handleQueryTypeChange = useCallback(
(queryType: ValuesQueryType) => {
onChangeQueryType(parameterId, queryType);
},
[parameterId, onChangeQueryType],
);
const handleSourceTypeChange = useCallback(
(sourceType: ValuesSourceType) => {
onChangeSourceType(parameterId, sourceType);
......@@ -120,6 +133,7 @@ const ParameterSidebar = ({
onChangeName={handleNameChange}
onChangeDefaultValue={handleDefaultValueChange}
onChangeIsMultiSelect={handleIsMultiSelectChange}
onChangeQueryType={handleQueryTypeChange}
onChangeSourceType={handleSourceTypeChange}
onChangeSourceConfig={handleSourceConfigChange}
onRemoveParameter={handleRemove}
......
export { default } from "./ParameterSourceSettings";
......@@ -2,7 +2,6 @@ import React, { useCallback, useMemo, useState } from "react";
import { ValuesSourceConfig, ValuesSourceType } from "metabase-types/api";
import { getNonVirtualFields } from "metabase-lib/parameters/utils/parameter-fields";
import { UiParameter } from "metabase-lib/parameters/types";
import { getSourceConfig, getSourceType } from "../../utils/dashboards";
import ValuesSourceTypeModal from "./ValuesSourceTypeModal";
import ValuesSourceCardModal from "./ValuesSourceCardModal";
......@@ -23,8 +22,10 @@ const ValuesSourceModal = ({
onClose,
}: ModalProps): JSX.Element => {
const [step, setStep] = useState<ModalStep>("main");
const [sourceType, setSourceType] = useState(getSourceType(parameter));
const [sourceConfig, setSourceConfig] = useState(getSourceConfig(parameter));
const [sourceType, setSourceType] = useState(parameter.values_source_type);
const [sourceConfig, setSourceConfig] = useState(
parameter.values_source_config,
);
const fields = useMemo(() => {
return getNonVirtualFields(parameter);
......
......@@ -2,31 +2,38 @@ import React, { useCallback, useMemo, useState } from "react";
import { t } from "ttag";
import Radio from "metabase/core/components/Radio/Radio";
import Modal from "metabase/components/Modal";
import { ValuesSourceConfig, ValuesSourceType } from "metabase-types/api";
import {
ValuesQueryType,
ValuesSourceConfig,
ValuesSourceType,
} from "metabase-types/api";
import { UiParameter } from "metabase-lib/parameters/types";
import ValuesSourceModal from "../ValuesSourceModal";
import {
RadioLabelButton,
RadioLabelRoot,
RadioLabelTitle,
} from "./ParameterSourceSettings.styled";
} from "./ValuesSourceSettings.styled";
export interface ParameterSourceSettingsProps {
export interface ValuesSourceSettingsProps {
parameter: UiParameter;
onChangeQueryType: (queryType: ValuesQueryType) => void;
onChangeSourceType: (sourceType: ValuesSourceType) => void;
onChangeSourceConfig: (sourceConfig: ValuesSourceConfig) => void;
}
const ParameterSourceSettings = ({
const ValuesSourceSettings = ({
parameter,
onChangeQueryType,
onChangeSourceType,
onChangeSourceConfig,
}: ParameterSourceSettingsProps): JSX.Element => {
}: ValuesSourceSettingsProps): JSX.Element => {
const queryType = parameter.values_query_type;
const [isModalOpened, setIsModalOpened] = useState(false);
const radioOptions = useMemo(() => {
return getRadioOptions(() => setIsModalOpened(true));
}, []);
return getRadioOptions(queryType, () => setIsModalOpened(true));
}, [queryType]);
const handleSubmit = useCallback(
(sourceType: ValuesSourceType, sourceConfig: ValuesSourceConfig) => {
......@@ -42,7 +49,12 @@ const ParameterSourceSettings = ({
return (
<>
<Radio value="list" options={radioOptions} vertical />
<Radio
value={queryType}
options={radioOptions}
vertical
onChange={onChangeQueryType}
/>
{isModalOpened && (
<Modal medium onClose={handleModalClose}>
<ValuesSourceModal
......@@ -58,25 +70,49 @@ const ParameterSourceSettings = ({
interface RadioLabelProps {
title: string;
onEditClick: () => void;
isSelected?: boolean;
onEditClick?: () => void;
}
const RadioLabel = ({ title, onEditClick }: RadioLabelProps): JSX.Element => {
const RadioLabel = ({
title,
isSelected,
onEditClick,
}: RadioLabelProps): JSX.Element => {
return (
<RadioLabelRoot>
<RadioLabelTitle>{title}</RadioLabelTitle>
<RadioLabelButton onClick={onEditClick}>{t`Edit`}</RadioLabelButton>
{isSelected && (
<RadioLabelButton onClick={onEditClick}>{t`Edit`}</RadioLabelButton>
)}
</RadioLabelRoot>
);
};
const getRadioOptions = (onEditClick: () => void) => {
const getRadioOptions = (
queryType: ValuesQueryType,
onEditClick: () => void,
) => {
return [
{
name: <RadioLabel title={t`Dropdown list`} onEditClick={onEditClick} />,
name: (
<RadioLabel
title={t`Dropdown list`}
isSelected={queryType === "list"}
onEditClick={onEditClick}
/>
),
value: "list",
},
{
name: <RadioLabel title={t`Search box`} />,
value: "search",
},
{
name: <RadioLabel title={t`Input box`} />,
value: "none",
},
];
};
export default ParameterSourceSettings;
export default ValuesSourceSettings;
export { default } from "./ValuesSourceSettings";
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment