Skip to content
Snippets Groups Projects
Unverified Commit 0dc75042 authored by Romeo Van Snick's avatar Romeo Van Snick Committed by GitHub
Browse files

Combine columns (#42021)

* [MLv2] Add `preview-expression` to eval an expression on sample data

This is needed to show the previews in the UX for the combining columns
epic #39977.

Fixes #39979.

* [MBQL lib] Add "Combine columns" drill-thru

This is following the original plan of using a drill for this. It's hard
to combine that with the "preview" functionality.

See [thread](https://metaboat.slack.com/archives/C06P22KS4JH/p1712264174056569)
for discussion of how we might approach that issue.

Fixes #39978.

* [MBQL lib] Add "Combine columns" drill-thru

This is following the original plan of using a drill for this. It's hard
to combine that with the "preview" functionality.

See [thread](https://metaboat.slack.com/archives/C06P22KS4JH/p1712264174056569

)
for discussion of how we might approach that issue.

Fixes #39978.

* [MLv2] Add `preview-expression` to eval an expression on sample data

This is needed to show the previews in the UX for the combining columns
epic #39977.

Fixes #39979.

* [MLv2] Add `preview-expression` to eval an expression on sample data

This is needed to show the previews in the UX for the combining columns
epic #39977.

Fixes #39979.

* Revert "Merge branch 'mblib-preview-expressions' into combine-columns"

This reverts commit dac17f84aaae09906a206106290f7f58dea659bd, reversing
changes made to 2934fab19db495ca8104801d41bc8aba457e401f.

* Revert "Merge branch 'mblib-preview-expressions' into combine-columns"

This reverts commit 2934fab19db495ca8104801d41bc8aba457e401f, reversing
changes made to 6a43c22e78fb616c7ac0b024811b2989a1ae0f20.

* Revert "Merge branch 'mblib-preview-expressions' into combine-columns"

This reverts commit 6a43c22e78fb616c7ac0b024811b2989a1ae0f20, reversing
changes made to 2257333a8ae69f0c9a6f6fdf0c8f5d837263f97a.

* [FE] "Combine columns" drill thru (#40082)

* Type Section['icon'] properly, including the hack for summarize section

* Add combine columns section

* Add types for combine columns drill

* Add combine columns drill component stub

* Mock combine columns drill

* Add component stub

* Encapsulate typing hacks

* Extract CombineColumnsDrill

* Extract ColumnAndSeparatorRow

* Improve naming, add button to edit separators

* Translate string

* Improve styling

* Separator input

* Handle removing columns

* Rename lib.ts to utils.ts

* Implement onSubmit

* Add getNextColumnAndSeparator

* Use form for a11y

* Handle vertical overflow

* Handle horizontal overflow

* Extract formatSeparator

* Use proper translations

* Adjust min/max sizes

* Fix scrollbar being unnecessarily shown

* Display source column name

* Avoid serializing the entire column and putting it into DOM

* Use module.css extension

* Format code

* Improve a11y

* Mock combineColumnsDrillExpression and previewExpression

* Add Preview component

* Move preview logic to Preview component

* Style Preview

* Format code

* Move styles to CSS modules

* Update UI to latest designs
- move "Add another column" button
- remove "Separated by X" button
- always show separator inputs

* Remove displayInfo mock

* Add asReturned

* Update CombineColumnsDrillThruInfo

* Integrate new way of working with combine columns drill

* Fix generating expression names

* Integrate drill with click actions

* Integrate new preview workflow

* Revert "Update UI to latest designs"

This reverts commit cda039dffe9d452c0866f63800e2e887a540fe67.

* Use previewExpression interface correctly

* Use new preview_expression API

* Make sure columns are created with original query and stage index

* Extract usePreview and handle preview errors

* Update popover title

* Move add column button

* Fix outline being cut off

* Style preview label as per design

* Format empty separator

* Make preview scrollable

* Use project convention

* Simplify ScrollArea usage

* Use ScrollArea in CombineColumnsDrill

* Update comment

* Remove Lib.previewExpression

* Clean up the preview after removing Lib.previewExpression

* Implement getPreview

* Change default column preview to 'text'

* Remove usePreview hook which is no longer in use

* Rename Preview to Example

* Use different placeholder for separator input

* Add whitespace placeholder

* Add ColumnPicker boilerplate

* Export Input

* Use QueryColumnPicker for ColumnPicker

* Pass width to AccordionList

* Add sequences popover events with tippy

* Remove unused ColumnOption helpers

* Add testid to Example

* Add e2e test for combine column in header

* Select content of separator input on focus

* Reduce padding on example

* Add monospace variant to Text

* Use monospace variant in Example

* Use monospace font in textinput

* Match faux-select label styles to our TextInput label styles

* Add missing color

* Remove !important

* Use pre whitespace so consecutive spaces show up

* Remove empty default

* Add example for boolean

* Remove !important

* Use should.have text over contain

---------

Co-authored-by: default avatarRomeo Van Snick <romeo@romeovansnick.be>

* Remove duplicated code

* Export useMantineTheme from metabase/ui

* Use useMantineTheme instead of reading theme directly

* Remove setTimeout in popover

* Use plain t instead of jt

* Move Popover dropdown sequence hack to metabase/ui

---------

Co-authored-by: default avatarBraden Shepherdson <braden@metabase.com>
Co-authored-by: default avatarKamil Mielnik <kamil@kamilmielnik.com>
parent 9887f222
No related branches found
No related tags found
No related merge requests found
import { t } from "ttag";
import * as Lib from "metabase-lib";
import type { Dataset, RowValues } from "metabase-types/api";
import type { ColumnAndSeparator } from "./types";
export const getNextColumnAndSeparator = (
expressionableColumns: Lib.ColumnMetadata[],
defaultSeparator: string,
columnsAndSeparators: ColumnAndSeparator[],
): ColumnAndSeparator => {
const lastSeparator = columnsAndSeparators.at(-1)?.separator;
const separator = lastSeparator ?? defaultSeparator;
const nextUnusedColumn = expressionableColumns.find(candidate =>
columnsAndSeparators.every(({ column }) => candidate !== column),
);
const result = nextUnusedColumn ?? expressionableColumns[0];
return { column: result, separator };
};
export const formatSeparator = (separator: string): string => {
if (separator.length === 0) {
return `(${t`empty`})`;
}
if (separator === " ") {
return `(${t`space`})`;
}
return separator;
};
export const extractQueryResults = (
query: Lib.Query,
stageIndex: number,
datasets: Dataset[] | null,
): {
columns: Lib.ColumnMetadata[];
rows: RowValues[];
} => {
if (!datasets || datasets.length === 0) {
return { columns: [], rows: [] };
}
const data = datasets[0].data;
const rows = data.rows;
const columns = data.results_metadata.columns.map(column => {
return Lib.fromLegacyColumn(query, stageIndex, column);
});
return { rows, columns };
};
export const getExample = (
column: Lib.ColumnMetadata,
columnsAndSeparators: ColumnAndSeparator[],
): string => {
return [
getColumnExample(column),
...columnsAndSeparators.flatMap(({ column, separator }) => [
separator,
getColumnExample(column),
]),
].join("");
};
const getColumnExample = (column: Lib.ColumnMetadata): string => {
if (Lib.isURL(column)) {
return "https://www.example.com";
}
if (Lib.isEmail(column)) {
return "email@example.com";
}
if (Lib.isID(column)) {
return "12345";
}
if (Lib.isBoolean(column)) {
return "true";
}
if (Lib.isNumeric(column)) {
return "123.45678901234567";
}
if (Lib.isDateWithoutTime(column)) {
return "2042-01-01";
}
if (Lib.isDate(column)) {
return "2042-01-01 12:34:56.789";
}
if (Lib.isTime(column)) {
return "12:34:56.789";
}
if (Lib.isLatitude(column) || Lib.isLongitude(column)) {
return "-12.34567";
}
return "text";
};
export const getDefaultSeparator = (column: Lib.ColumnMetadata): string => {
if (Lib.isURL(column)) {
return "/";
}
if (Lib.isEmail(column)) {
return "";
}
return " ";
};
export const getDrillExpressionClause = (
column: Lib.ColumnMetadata,
columnsAndSeparators: ColumnAndSeparator[],
) => {
return Lib.expressionClause("concat", [
column,
...columnsAndSeparators.flatMap(({ column, separator }) => [
separator,
column,
]),
]);
};
export const getExpressionName = (
query: Lib.Query,
stageIndex: number,
column: Lib.ColumnMetadata,
columnsAndSeparators: ColumnAndSeparator[],
): string => {
const columns = [column, ...columnsAndSeparators.map(({ column }) => column)];
const names = columns.map(
column => Lib.displayInfo(query, stageIndex, column).displayName,
);
return names.join(" ");
};
......@@ -4,6 +4,7 @@ import type * as Lib from "metabase-lib";
import { automaticInsightsDrill } from "./automatic-insights-drill";
import { columnExtractDrill } from "./column-extract-drill";
import { columnFilterDrill } from "./column-filter-drill";
import { combineColumnsDrill } from "./combine-columns-drill";
import { distributionDrill } from "./distribution-drill";
import { fkDetailsDrill } from "./fk-details-drill";
import { fkFilterDrill } from "./fk-filter-drill";
......@@ -23,6 +24,7 @@ export const DRILLS: Record<Lib.DrillThruType, Drill<any>> = {
"drill-thru/automatic-insights": automaticInsightsDrill,
"drill-thru/column-extract": columnExtractDrill,
"drill-thru/column-filter": columnFilterDrill,
"drill-thru/combine-columns": combineColumnsDrill,
"drill-thru/distribution": distributionDrill,
"drill-thru/fk-details": fkDetailsDrill,
"drill-thru/fk-filter": fkFilterDrill,
......
import type { PopoverDropdownProps } from "@mantine/core";
import { Popover } from "@mantine/core";
import { Popover as MantinePopover } from "@mantine/core";
import { useEffect } from "react";
export type { PopoverBaseProps, PopoverProps } from "@mantine/core";
export { getPopoverOverrides } from "./Popover.styled";
const MantinePopoverDropdown = Popover.Dropdown;
import useSequencedContentCloseHandler from "metabase/hooks/use-sequenced-content-close-handler";
const MantinePopoverDropdown = MantinePopover.Dropdown;
type ExtendedPopoverDropdownProps = PopoverDropdownProps & {
// Prevent parent TippyPopover from closing when selecting an item
// TODO: remove when TippyPopover is no longer used
setupSequencedCloseHandler?: boolean;
};
const PopoverDropdown = function PopoverDropdown(
props: ExtendedPopoverDropdownProps,
) {
const { setupCloseHandler, removeCloseHandler } =
useSequencedContentCloseHandler();
useEffect(() => {
if (!props.setupSequencedCloseHandler) {
return;
}
setupCloseHandler(document.body, () => undefined);
return () => removeCloseHandler();
}, [setupCloseHandler, removeCloseHandler, props.setupSequencedCloseHandler]);
const PopoverDropdown = function PopoverDropdown(props: PopoverDropdownProps) {
return (
<MantinePopoverDropdown {...props} data-element-id="mantine-popover" />
);
};
PopoverDropdown.displayName = MantinePopoverDropdown.displayName;
Popover.Dropdown = PopoverDropdown;
MantinePopover.Dropdown = PopoverDropdown;
const Popover: typeof MantinePopover & {
Dropdown: typeof PopoverDropdown;
} = MantinePopover;
export { Popover };
export { rem } from "@mantine/core";
export { rem, useMantineTheme } from "@mantine/core";
export type { TabsValue } from "@mantine/core";
export { useHover } from "@mantine/hooks";
export * from "./components";
......@@ -19,6 +19,8 @@ export const SECTIONS: Record<ClickActionSection, Section> = {
standalone_filter: {},
summarize: {},
sum: {},
combine: {},
"combine-popover": {},
extract: {},
"extract-popover": {},
auto: {},
......
......@@ -27,6 +27,8 @@ export type ClickActionSection =
| "auto-popover"
| "breakout"
| "breakout-popover"
| "combine"
| "combine-popover"
| "details"
| "extract"
| "extract-popover"
......
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