Skip to content
Snippets Groups Projects
Unverified Commit 525b565c authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Smart Filter Field Order (#22963)

* sort filter fields

* add tests

* add e2e tests

* enable ellipsified test ids

* dont mutate sematic_type
parent 6ee3474c
No related branches found
No related tags found
No related merge requests found
......@@ -46,6 +46,8 @@ class FieldInner extends Base {
id: number | FieldRef;
name: string;
semantic_type: string | null;
fingerprint: any;
base_type: string | null;
table?: Table;
target?: Field;
......
......@@ -16,6 +16,7 @@ const clampCss = (props: EllipsifiedRootProps) => css`
interface EllipsifiedRootProps {
lines?: number;
"data-testId"?: string;
}
export const EllipsifiedRoot = styled.div<EllipsifiedRootProps>`
......
......@@ -16,6 +16,7 @@ interface EllipsifiedProps {
tooltipMaxWidth?: React.CSSProperties["maxWidth"];
lines?: number;
placement?: Placement;
"data-testid"?: string;
}
const Ellipsified = ({
......@@ -28,6 +29,7 @@ const Ellipsified = ({
tooltipMaxWidth,
lines,
placement = "top",
"data-testid": dataTestId,
}: EllipsifiedProps) => {
const [isTruncated, setIsTruncated] = useState(false);
const rootRef = useRef<HTMLDivElement | null>(null);
......@@ -62,6 +64,7 @@ const Ellipsified = ({
className={className}
lines={lines}
style={style}
data-testid={dataTestId}
>
{children}
</EllipsifiedRoot>
......
......@@ -8,7 +8,6 @@ import StructuredQuery, {
isSegmentOption,
} from "metabase-lib/lib/queries/StructuredQuery";
import Dimension from "metabase-lib/lib/Dimension";
import { isSegment } from "metabase/lib/query/filter";
import { ModalDivider } from "../BulkFilterModal/BulkFilterModal.styled";
import Filter from "metabase-lib/lib/queries/structured/Filter";
import { BulkFilterSelect, SegmentFilterSelect } from "../BulkFilterSelect";
......@@ -18,6 +17,7 @@ import {
ListRowContent,
ListRowLabel,
} from "./BulkFilterList.styled";
import { sortDimensions } from "./utils";
export interface BulkFilterListProps {
query: StructuredQuery;
......@@ -39,7 +39,10 @@ const BulkFilterList = ({
onClearSegments,
}: BulkFilterListProps): JSX.Element => {
const [dimensions, segments] = useMemo(
() => [options.filter(isDimensionOption), options.filter(isSegmentOption)],
() => [
options.filter(isDimensionOption).sort(sortDimensions),
options.filter(isSegmentOption),
],
[options],
);
......@@ -92,7 +95,9 @@ const BulkFilterListItem = ({
return (
<ListRow>
<ListRowLabel>{dimension.displayName()}</ListRowLabel>
<ListRowLabel data-testid="dimension-filter-label">
{dimension.displayName()}
</ListRowLabel>
<ListRowContent>
{options.map((filter, index) => (
<BulkFilterSelect
......
import { DimensionOption } from "metabase-lib/lib/queries/StructuredQuery";
const LONG_TEXT_MIN = 80;
type PriorityMap = { [key: string]: number | undefined };
const fieldSortPriorities: PriorityMap = {
"type/CreationTemporal": 10,
"type/CreationTimestamp": 10,
"type/CreationDate": 10,
"type/CreationTime": 11,
"type/Boolean": 20,
"type/Category": 30,
"type/Currency": 40,
"type/Price": 40,
"type/Discount": 40,
"type/GrossMargin": 40,
"type/Cost": 40,
"type/Location": 50,
"type/Address": 50,
"type/City": 51,
"type/State": 52,
"type/ZipCode": 53,
"type/Country": 54,
"type/Number": 60,
"type/Float": 60,
"type/BigInteger": 60,
"type/Integer": 60,
"type/Text": 70,
"type/Date": 80,
"type/PK": 90,
"type/Latitude": 210,
"type/Longitude": 211,
"type/LongText": 220, // not a "real" metabase type, but having it as part of this list makes it easier to sort
"type/FK": 230,
"type/JSON": 240,
"": undefined,
};
const getSortValue = (dimensionOption: DimensionOption): number => {
const field = dimensionOption.dimension.field();
const isLongText =
field?.fingerprint?.type?.["type/Text"]?.["average-length"] >=
LONG_TEXT_MIN;
return (
(isLongText ? fieldSortPriorities["type/LongText"] : undefined) ??
fieldSortPriorities[field.semantic_type ?? ""] ??
fieldSortPriorities[field.base_type ?? ""] ??
900
);
};
export const sortDimensions = (a: DimensionOption, b: DimensionOption) =>
getSortValue(a) - getSortValue(b);
import { sortDimensions } from "./utils";
import Dimension from "metabase-lib/lib/Dimension";
import { DimensionOption } from "metabase-lib/lib/queries/StructuredQuery";
const mockDimensionOption = (
semantic_type: string,
base_type: string,
text_length: number = 0,
): DimensionOption => {
const dimension = {
field: () => ({
semantic_type,
base_type,
fingerprint: {
type: {
"type/Text": {
"average-length": text_length,
},
},
},
}),
} as any;
return { dimension } as DimensionOption;
};
describe("sortDimensionOptions", () => {
it("should sort created-at fields before numbers", () => {
const sorted = [
mockDimensionOption("", "type/Float"),
mockDimensionOption("type/CreationTimestamp", "type/Text"),
mockDimensionOption("", "type/Float"),
].sort(sortDimensions);
expect(sorted[0].dimension.field().semantic_type).toBe(
"type/CreationTimestamp",
);
});
it("should sort latitudes before jsons", () => {
const sorted = [
mockDimensionOption("", "type/JSON"),
mockDimensionOption("type/Latitude", "type/Float"),
].sort(sortDimensions);
expect(sorted[0].dimension.field().semantic_type).toBe("type/Latitude");
});
it("should sort city before primary key", () => {
const sorted = [
mockDimensionOption("type/PK", "type/BigInteger"),
mockDimensionOption("type/City", "type/Text"),
].sort(sortDimensions);
expect(sorted[0].dimension.field().semantic_type).toBe("type/City");
});
it("should sort booleans before categories", () => {
const sorted = [
mockDimensionOption("type/Category", "type/Text"),
mockDimensionOption("", "type/Boolean"),
].sort(sortDimensions);
expect(sorted[0].dimension.field().base_type).toBe("type/Boolean");
});
it("should sort short text before long text", () => {
const sorted = [
mockDimensionOption("", "type/Text", 400),
mockDimensionOption("", "type/Text", 10),
mockDimensionOption("", "type/Text", 200),
].sort(sortDimensions);
expect(sorted[0].dimension.field().base_type).toBe("type/Text");
expect(
sorted[0].dimension.field().fingerprint.type["type/Text"][
"average-length"
],
).toBe(10);
expect(
sorted[1].dimension.field().fingerprint.type["type/Text"][
"average-length"
],
).toBe(400);
});
});
......@@ -56,6 +56,25 @@ describe("scenarios > filters > bulk filtering", () => {
cy.signInAsAdmin();
});
it("should sort database fields by relevance", () => {
visitQuestionAdhoc(rawQuestionDetails);
openFilterModal();
modal().within(() => {
cy.findAllByTestId("dimension-filter-label")
.eq(0)
.should("have.text", "Created At");
cy.findAllByTestId("dimension-filter-label")
.eq(1)
.should("have.text", "Discount");
cy.findAllByTestId("dimension-filter-label")
.last()
.should("include.text", "ID");
});
});
it("should add a filter for a raw query", () => {
visitQuestionAdhoc(rawQuestionDetails);
openFilterModal();
......
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