Skip to content
Snippets Groups Projects
Unverified Commit edd0faad authored by Nick Fitzpatrick's avatar Nick Fitzpatrick Committed by GitHub
Browse files

Show hide all table columns (#27689)

* rename onShowWidget

* temp

* POC

* temp

* Rework logic to use dimensions rather than pass around mbql

* PR Cleanup and feedback

* adjusting e2e tests

* Add unit tests

* cleaning up unit tests

* Hiding disabled columns from sorting list

* breaking down field options function

* passing  to settings def and column editor component

* Adding e2e tests, adjusting to fix 13455

* shame

* removing unneeded import
parent caa9d6e6
Branches
Tags
No related merge requests found
Showing
with 480 additions and 204 deletions
......@@ -63,15 +63,17 @@ export function findColumnIndexForColumnSetting(columns, columnSetting) {
export function findColumnSettingIndexForColumn(columnSettings, column) {
const fieldRef = normalizeFieldRef(fieldRefForColumn(column));
if (fieldRef == null) {
return columnSettings.findIndex(
columnSetting => columnSetting.name === column.name,
if (fieldRef !== null) {
const index = columnSettings.findIndex(columnSetting =>
_.isEqual(fieldRef, normalizeFieldRef(columnSetting.fieldRef)),
);
if (index >= 0) {
return index;
}
}
const index = columnSettings.findIndex(columnSetting =>
_.isEqual(fieldRef, normalizeFieldRef(columnSetting.fieldRef)),
return columnSettings.findIndex(
columnSetting => columnSetting.name === column.name,
);
return index;
}
export function syncTableColumnsToQuery(question) {
......
......@@ -291,6 +291,7 @@ function DashCard({
}
showClickBehaviorSidebar={handleShowClickBehaviorSidebar}
onPreviewToggle={handlePreviewToggle}
metadata={metadata}
/>
</DashboardCardActionsPanel>
);
......@@ -310,6 +311,7 @@ function DashCard({
onReplaceAllVisualizationSettings,
handlePreviewToggle,
handleShowClickBehaviorSidebar,
metadata,
]);
return (
......
......@@ -11,6 +11,7 @@ import {
VisualizationSettings,
} from "metabase-types/api";
import { Series } from "metabase-types/types/Visualization";
import Metadata from "metabase-lib/metadata/Metadata";
import DashCardActionButton from "./DashCardActionButton";
......@@ -19,6 +20,7 @@ interface Props {
dashboard: Dashboard;
dashcard?: DashboardOrderedCard;
onReplaceAllVisualizationSettings: (settings: VisualizationSettings) => void;
metadata: Metadata;
}
function ChartSettingsButton({
......@@ -26,6 +28,7 @@ function ChartSettingsButton({
dashboard,
dashcard,
onReplaceAllVisualizationSettings,
metadata,
}: Props) {
return (
<ModalWithTrigger
......@@ -45,6 +48,7 @@ function ChartSettingsButton({
isDashboard
dashboard={dashboard}
dashcard={dashcard}
metadata={metadata}
/>
</ModalWithTrigger>
);
......
......@@ -12,6 +12,8 @@ import {
} from "metabase-types/api";
import { Series } from "metabase-types/types/Visualization";
import Metadata from "metabase-lib/metadata/Metadata";
import DashCardActionButton from "./DashCardActionButton";
import AddSeriesButton from "./AddSeriesButton";
......@@ -32,6 +34,7 @@ interface Props {
onReplaceAllVisualizationSettings: (settings: VisualizationSettings) => void;
showClickBehaviorSidebar: () => void;
onPreviewToggle: () => void;
metadata: Metadata;
}
function DashCardActionButtons({
......@@ -47,6 +50,7 @@ function DashCardActionButtons({
onReplaceAllVisualizationSettings,
showClickBehaviorSidebar,
onPreviewToggle,
metadata,
}: Props) {
const { disableSettingsConfig, supportPreviewing, supportsSeries } =
getVisualizationRaw(series).visualization;
......@@ -77,6 +81,7 @@ function DashCardActionButtons({
series={series}
dashboard={dashboard}
dashcard={dashcard}
metadata={metadata}
onReplaceAllVisualizationSettings={onReplaceAllVisualizationSettings}
/>,
);
......
......@@ -22,6 +22,7 @@ export default class ChartSettingsSidebar extends React.Component {
onClose,
onOpenChartType,
visualizationSettings,
isRunning,
} = this.props;
const { sidebarPropsOverride } = this.state;
return (
......@@ -48,6 +49,7 @@ export default class ChartSettingsSidebar extends React.Component {
initial={initialChartSetting}
setSidebarPropsOverride={this.setSidebarPropsOverride}
computedSettings={visualizationSettings}
isQueryRunning={isRunning}
/>
</SidebarContent>
)
......
......@@ -30,7 +30,11 @@ import { getColumnKey } from "metabase-lib/queries/utils/get-column-key";
import ChartSettingsWidgetList from "./ChartSettingsWidgetList";
import ChartSettingsWidgetPopover from "./ChartSettingsWidgetPopover";
import { SectionContainer, SectionWarnings } from "./ChartSettings.styled";
import {
SectionContainer,
SectionWarnings,
TitleButton,
} from "./ChartSettings.styled";
// section names are localized
const DEFAULT_TAB_PRIORITY = [t`Data`];
......@@ -45,6 +49,7 @@ const withTransientSettingState = ComposedComponent =>
super(props);
this.state = {
settings: props.settings,
overrideProps: {},
};
}
......@@ -63,6 +68,10 @@ const withTransientSettingState = ComposedComponent =>
onDone={settings =>
this.props.onChange(settings || this.state.settings)
}
setSidebarPropsOverride={overrideProps =>
this.setState({ overrideProps })
}
{...this.state.overrideProps}
/>
);
}
......@@ -73,7 +82,7 @@ class ChartSettings extends Component {
super(props);
this.state = {
currentSection: (props.initial && props.initial.section) || null,
currentWidget: (props.initial && props.initial.widget) || null,
popoverWidget: (props.initial && props.initial.widget) || null,
};
}
......@@ -82,23 +91,38 @@ class ChartSettings extends Component {
if (!_.isEqual(initial, prevProps.initial)) {
this.setState({
currentSection: (initial && initial.section) || null,
currentWidget: (initial && initial.widget) || null,
popoverWidget: (initial && initial.widget) || null,
});
}
}
handleShowSection = section => {
this.setState({ currentSection: section, currentWidget: null });
this.setState({ currentSection: section, popoverWidget: null });
};
// allows a widget to temporarily replace itself with a different widget
handleShowWidget = (widget, ref) => {
this.setState({ popoverRef: ref, currentWidget: widget });
handleShowPopoverWidget = (widget, ref) => {
this.setState({ popoverRef: ref, popoverWidget: widget });
};
handleSetCurrentWidget = (widget, title) => {
this.props.setSidebarPropsOverride({
title: title,
onBack: () => {
this.handleEndShowWidget();
this.props.setSidebarPropsOverride({});
},
});
this.setState({ currentWidget: widget });
};
// go back to previously selected section
handleEndShowWidget = () => {
this.setState({ currentWidget: null, popoverRef: null });
this.setState({
popoverWidget: null,
popoverRef: null,
currentWidget: null,
});
};
handleResetSettings = () => {
......@@ -141,13 +165,14 @@ class ChartSettings extends Component {
if (this.props.widgets) {
return this.props.widgets;
} else {
const { isDashboard } = this.props;
const { isDashboard, metadata, isQueryRunning = false } = this.props;
const transformedSeries = this._getTransformedSeries();
return getSettingsWidgetsForSeries(
transformedSeries,
this.handleChangeSettings,
isDashboard,
{ metadata, isQueryRunning },
);
}
}
......@@ -193,24 +218,24 @@ class ChartSettings extends Component {
const widgets = this._getWidgets();
const series = this._getTransformedSeries();
const settings = this._getComputedSettings();
const { currentWidget } = this.state;
const { popoverWidget } = this.state;
const seriesSettingsWidget =
currentWidget && widgets.find(widget => widget.id === "series_settings");
popoverWidget && widgets.find(widget => widget.id === "series_settings");
//We don't want to show series settings widget for waterfall charts
if (series?.[0]?.card?.display === "waterfall" || !seriesSettingsWidget) {
return null;
}
if (currentWidget.props?.seriesKey !== undefined) {
if (popoverWidget.props?.seriesKey !== undefined) {
return {
...seriesSettingsWidget,
props: {
...seriesSettingsWidget.props,
initialKey: currentWidget.props.seriesKey,
initialKey: popoverWidget.props.seriesKey,
},
};
} else if (currentWidget.props?.initialKey) {
} else if (popoverWidget.props?.initialKey) {
const hasBreakouts = settings["graph.dimensions"]?.length > 1;
if (hasBreakouts) {
......@@ -220,7 +245,7 @@ class ChartSettings extends Component {
const singleSeriesForColumn = series.find(single => {
const metricColumn = single.data.cols[1];
if (metricColumn) {
return getColumnKey(metricColumn) === currentWidget.props.initialKey;
return getColumnKey(metricColumn) === popoverWidget.props.initialKey;
}
});
......@@ -240,12 +265,12 @@ class ChartSettings extends Component {
getFormattingWidget = () => {
const widgets = this._getWidgets();
const { currentWidget } = this.state;
const { popoverWidget } = this.state;
const widget =
currentWidget && widgets.find(widget => widget.id === currentWidget.id);
popoverWidget && widgets.find(widget => widget.id === popoverWidget.id);
if (widget) {
return { ...widget, props: { ...widget.props, ...currentWidget.props } };
return { ...widget, props: { ...widget.props, ...popoverWidget.props } };
}
return null;
......@@ -260,8 +285,10 @@ class ChartSettings extends Component {
dashboard,
dashcard,
isDashboard,
title,
onBack,
} = this.props;
const { currentWidget, popoverRef } = this.state;
const { popoverWidget, popoverRef, currentWidget } = this.state;
const settings = this._getSettings();
const widgets = this._getWidgets();
......@@ -308,7 +335,14 @@ class ChartSettings extends Component {
: _.find(DEFAULT_TAB_PRIORITY, name => name in sections) ||
sectionNames[0];
const visibleWidgets = sections[currentSection] || [];
const visibleWidgets =
(currentWidget
? [
widgets.find(
widget => widget.id === currentWidget.props.initialKey,
),
].map(w => ({ ...w, hidden: false }))
: sections[currentSection]) || [];
// This checks whether the current section contains a column settings widget
// at the top level. If it does, we avoid hiding the section tabs and
......@@ -321,7 +355,8 @@ class ChartSettings extends Component {
// NOTE: special props to support adding additional fields
question: question,
addField: addField,
onShowWidget: this.handleShowWidget,
onShowPopoverWidget: this.handleShowPopoverWidget,
onSetCurrentWidget: this.handleSetCurrentWidget,
onEndShowWidget: this.handleEndShowWidget,
currentSectionHasColumnSettings,
columnHasSettings: col => this.columnHasSettings(col),
......@@ -357,7 +392,8 @@ class ChartSettings extends Component {
visibleWidgets[0].id === "column_settings" &&
// and this section doesn't doesn't have that as a direct child
!currentSectionHasColumnSettings
);
) &&
!currentWidget;
// default layout with visualization
return (
......@@ -384,6 +420,11 @@ class ChartSettings extends Component {
className="Grid-cell Cell--1of3 scroll-y scroll-show border-right py4"
data-testid="chartsettings-sidebar"
>
{title && (
<TitleButton onClick={onBack} icon="chevronleft" onlyText>
{title}
</TitleButton>
)}
<ChartSettingsWidgetList
widgets={visibleWidgets}
extraWidgetProps={extraWidgetProps}
......@@ -422,7 +463,7 @@ class ChartSettings extends Component {
)}
<ChartSettingsWidgetPopover
currentWidgetKey={
currentWidget?.props?.initialKey || currentWidget?.props?.seriesKey
popoverWidget?.props?.initialKey || popoverWidget?.props?.seriesKey
}
anchor={popoverRef}
widgets={[this.getFormattingWidget(), this.getStyleWidget()].filter(
......
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import Radio from "metabase/core/components/Radio";
import Button from "metabase/core/components/Button";
import Warnings from "metabase/query_builder/components/Warnings";
export const SectionContainer = styled.div`
......@@ -16,3 +17,16 @@ export const SectionContainer = styled.div`
export const SectionWarnings = styled(Warnings)`
color: ${color("accent4")};
`;
export const TitleButton = styled(Button)`
margin-left: 1.5rem;
margin-bottom: 0.5rem;
${Button.Content} {
color: ${color("text-dark")};
&:hover {
color: ${color("brand")};
}
font-size: 1rem;
}
`;
......@@ -14,6 +14,7 @@ export const Root = styled.div<{
marginBottom?: string;
borderBottom?: boolean;
}>`
position: relative;
${props =>
!props.noPadding &&
css`
......
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import CheckBox from "metabase/core/components/CheckBox";
export const TableName = styled.p`
text-transform: uppercase;
color: ${color("text-medium")};
display: inline-block;
font-weight: 700;
`;
export const TableHeaderContainer = styled.div`
display: flex;
justify-content: space-between;
`;
export const FieldCheckbox = styled(CheckBox)`
${CheckBox.Label} {
font-weight: 700;
}
margin-bottom: 0.5rem;
`;
import React, { useMemo } from "react";
import { t } from "ttag";
import _ from "underscore";
import Button from "metabase/core/components/Button";
import { DatasetColumn } from "metabase-types/api";
import { ConcreteField, Field } from "metabase-types/types/Query";
import { isNotNull } from "metabase/core/utils/types";
import Metadata from "metabase-lib/metadata/Metadata";
import StructuredQuery from "metabase-lib/queries/StructuredQuery";
import NativeQuery from "metabase-lib/queries/NativeQuery";
import Question from "metabase-lib/Question";
import Dimension from "metabase-lib/Dimension";
import {
TableHeaderContainer,
TableName,
FieldCheckbox,
} from "./ChartSettingColumnEditor.styled";
type ColumnSetting = {
name?: string;
fieldRef?: Field | null;
enabled: boolean;
};
interface ChartSettingColumnEditorProps {
value: ColumnSetting[];
onChange: (val: ColumnSetting[]) => void;
columns: DatasetColumn[];
question: Question;
isDashboard?: boolean;
metadata?: Metadata;
isQueryRunning: boolean;
}
const structuredQueryFieldOptions = (
query: StructuredQuery,
columns: DatasetColumn[],
) => {
const options = query.fieldsOptions();
const allFields = options.dimensions.concat(
options.fks.reduce(
(memo, fk) => memo.concat(fk.dimensions),
[] as Dimension[],
),
);
const missingDimensions = columns
.filter(
column =>
!allFields.some(dimension =>
dimension.isSameBaseDimension(column.field_ref as ConcreteField),
),
)
.map(column =>
Dimension.parseMBQL(
column.field_ref as ConcreteField,
query.metadata(),
query,
),
)
.filter(isNotNull);
return {
name: query.table()?.display_name || t`Columns`,
dimensions: options.dimensions.concat(missingDimensions).filter(isNotNull),
fks: options.fks.map(fk => ({
name:
fk.name ||
(fk.field.target
? fk.field.target.table?.display_name
: fk.field.displayName()),
dimensions: fk.dimensions.filter(isNotNull),
})),
};
};
const nativeQueryFieldOptions = (
columns: DatasetColumn[],
metadata?: Metadata,
) => {
const allDimensions = columns
.map(column =>
Dimension.parseMBQL(column.field_ref as ConcreteField, metadata),
)
.filter(isNotNull);
const groupedDimensions = _.groupBy(
allDimensions,
dimension => dimension.field().table?.displayName() || t`Columns`,
);
const tables = Object.keys(groupedDimensions);
const firstTable = tables[0];
return {
name: firstTable,
dimensions: groupedDimensions[firstTable],
fks: tables
.filter(table => table !== firstTable)
.map(table => ({
name: table,
dimensions: groupedDimensions[table],
})),
};
};
const ChartSettingColumnEditor = ({
onChange,
value: columnSettings,
columns,
question,
isDashboard,
metadata,
isQueryRunning,
}: ChartSettingColumnEditorProps) => {
const fieldOptions = useMemo(() => {
const query = question && question.query();
if (query instanceof StructuredQuery && !isDashboard) {
return structuredQueryFieldOptions(query, columns);
} else if ((query instanceof NativeQuery || isDashboard) && columns) {
return nativeQueryFieldOptions(columns, metadata);
} else {
return {
name: "",
dimensions: [],
fks: [],
};
}
}, [question, columns, isDashboard, metadata]);
const getColumnSettingByDimension = (dimension: Dimension) => {
return columnSettings.find(setting =>
dimension.isSameBaseDimension(setting.fieldRef as ConcreteField),
);
};
const columnIsEnabled = (dimension: Dimension) => {
return getColumnSettingByDimension(dimension)?.enabled;
};
const toggleColumn = (dimension: Dimension) => {
const setting = getColumnSettingByDimension(dimension);
if (!setting?.enabled) {
enableColumns([dimension]);
} else {
disableColumns([dimension]);
}
};
const tableInColumnSettings = (dimensions: Dimension[]) => {
return dimensions.some(
dimension => getColumnSettingByDimension(dimension)?.enabled,
);
};
const disableColumns = (dimensions: Dimension[]) => {
const columnSettingsCopy = columnSettings.map(setting => ({
...setting,
enabled: dimensions.some(dimension =>
dimension.isSameBaseDimension(setting.fieldRef as ConcreteField),
)
? false
: setting.enabled,
}));
onChange(columnSettingsCopy);
};
const enableColumns = (dimensions: Dimension[]) => {
const [dimensionsInColumnSettings, dimensionsNotInColumnSettings] =
_.partition(
dimensions,
dimension => !!getColumnSettingByDimension(dimension),
);
//Enable any columns that are in column settings (used for Native Queries)
const columnSettingsCopy = columnSettings.map(setting => ({
...setting,
enabled: dimensionsInColumnSettings.some(dimension =>
dimension.isSameBaseDimension(setting.fieldRef as ConcreteField),
)
? true
: setting.enabled,
}));
//Add any that are not in the ColumnSettings (used for Structured Queries)
onChange([
...columnSettingsCopy,
...dimensionsNotInColumnSettings.map(dimension => ({
fieldRef: dimension.mbql(),
enabled: true,
})),
]);
};
return (
<div className="list">
<TableHeaderContainer>
<TableName>{fieldOptions.name}</TableName>
<BulkActionButton
tableInColumns={tableInColumnSettings(fieldOptions.dimensions)}
bulkEnable={() => enableColumns(fieldOptions.dimensions)}
bulkDisable={() => disableColumns(fieldOptions.dimensions)}
testid={`bulk-action-${fieldOptions.name}`}
/>
</TableHeaderContainer>
{fieldOptions.dimensions.map((dimension, index) => (
<FieldCheckbox
label={dimension.displayName()}
onClick={() => toggleColumn(dimension)}
checked={columnIsEnabled(dimension)}
key={`${dimension.displayName()}-${index}`}
disabled={isQueryRunning}
/>
))}
{fieldOptions.fks.map(fk => (
<>
<TableHeaderContainer>
<TableName>{fk.name}</TableName>
<BulkActionButton
tableInColumns={tableInColumnSettings(fk.dimensions)}
bulkEnable={() => enableColumns(fk.dimensions)}
bulkDisable={() => disableColumns(fk.dimensions)}
testid={`bulk-action-${fk.name}`}
/>
</TableHeaderContainer>
{fk.dimensions.map((dimension, index) => (
<FieldCheckbox
label={dimension.displayName()}
onClick={() => toggleColumn(dimension)}
checked={columnIsEnabled(dimension)}
key={`${fk.name}-${dimension.displayName()}-${index}`}
disabled={isQueryRunning}
/>
))}
</>
))}
</div>
);
};
interface BulkActionButtonProps {
tableInColumns: boolean;
bulkEnable: () => void;
bulkDisable: () => void;
testid: string;
}
const BulkActionButton = ({
tableInColumns,
bulkEnable,
bulkDisable,
testid,
}: BulkActionButtonProps) =>
tableInColumns ? (
<Button onlyText onClick={bulkDisable} data-testid={testid}>
{t`Deselect All`}
</Button>
) : (
<Button onlyText onClick={bulkEnable} data-testid={testid}>
{t`Select All`}
</Button>
);
export default ChartSettingColumnEditor;
......@@ -16,7 +16,7 @@ const ChartSettingFieldPicker = ({
options,
onChange,
onRemove,
onShowWidget,
onShowPopoverWidget,
className,
columns,
showColumnSetting,
......@@ -76,7 +76,7 @@ const ChartSettingFieldPicker = ({
<SettingsIcon
name="ellipsis"
onClick={e => {
onShowWidget(
onShowPopoverWidget(
{
id: "column_settings",
props: {
......
......@@ -36,7 +36,7 @@ class ChartSettingFieldsPartition extends React.Component {
handleEditFormatting = (column, targetElement) => {
if (column) {
this.props.onShowWidget(
this.props.onShowPopoverWidget(
{
id: "column_settings",
props: {
......
......@@ -38,6 +38,7 @@ const ChartSettingFieldsPicker = ({
{provided => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{fields.map((field, fieldIndex) => {
const lastField = fieldIndex === fields.length - 1;
return (
<Draggable
key={`draggable-${field}`}
......@@ -49,7 +50,7 @@ const ChartSettingFieldsPicker = ({
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className="mb1"
className={!lastField && "mb1"}
>
<ChartSettingFieldPicker
{...props}
......
/* eslint-disable react/prop-types */
import React, { Component } from "react";
import { t } from "ttag";
import _ from "underscore";
import { getFriendlyName } from "metabase/visualizations/lib/utils";
import { getColumnKey } from "metabase-lib/queries/utils/get-column-key";
import { findColumnForColumnSetting } from "metabase-lib/queries/utils/dataset";
import StructuredQuery from "metabase-lib/queries/StructuredQuery";
import ColumnItem from "./ColumnItem";
import { ChartSettingOrderedItems } from "./ChartSettingOrderedItems";
export default class ChartSettingOrderedColumns extends Component {
handleEnable = columnSetting => {
const columnSettings = [...this.props.value];
const index = columnSetting.index;
columnSettings[index] = { ...columnSettings[index], enabled: true };
this.props.onChange(columnSettings);
};
handleDisable = columnSetting => {
const columnSettings = [...this.props.value];
const index = columnSetting.index;
columnSettings[index] = { ...columnSettings[index], enabled: false };
this.props.onChange(columnSettings);
};
handleSortEnd = ({ oldIndex, newIndex }) => {
const fields = [...this.props.value];
fields.splice(newIndex, 0, fields.splice(oldIndex, 1)[0]);
this.props.onChange(fields);
};
handleEdit = (columnSetting, targetElement) => {
const column = findColumnForColumnSetting(
this.props.columns,
columnSetting,
);
if (column) {
this.props.onShowWidget(
{
id: "column_settings",
props: {
initialKey: getColumnKey(column),
},
},
targetElement,
);
}
};
handleAddNewField = fieldRef => {
const { value, onChange } = this.props;
const columnSettings = [...value, { fieldRef, enabled: true }];
onChange(columnSettings);
};
getColumnName = columnSetting =>
getFriendlyName(
findColumnForColumnSetting(this.props.columns, columnSetting) || {
display_name: "[Unknown]",
},
);
render() {
const { value, question, columns } = this.props;
const query = question && question.query();
let additionalFieldOptions = { count: 0 };
if (columns && query instanceof StructuredQuery) {
additionalFieldOptions = query.fieldsOptions(dimension => {
return !_.find(columns, column =>
dimension.isSameBaseDimension(column.field_ref),
);
});
}
const [enabledColumns, disabledColumns] = _.partition(
value
.filter(columnSetting =>
findColumnForColumnSetting(columns, columnSetting),
)
.map((columnSetting, index) => ({ ...columnSetting, index })),
columnSetting => columnSetting.enabled,
);
return (
<div className="list">
{enabledColumns.length > 0 ? (
<ChartSettingOrderedItems
items={enabledColumns}
getItemName={this.getColumnName}
onEdit={this.handleEdit}
onRemove={this.handleDisable}
onSortEnd={this.handleSortEnd}
distance={5}
/>
) : (
<div className="my2 p2 flex layout-centered bg-grey-0 text-light text-bold rounded">
{t`Add fields from the list below`}
</div>
)}
{disabledColumns.length > 0 || additionalFieldOptions.count > 0 ? (
<h4 className="mb2 mt4 pt4 border-top">{t`More columns`}</h4>
) : null}
{disabledColumns.map((columnSetting, index) => (
<ColumnItem
key={index}
title={this.getColumnName(columnSetting)}
onAdd={() => this.handleEnable(columnSetting)}
onClick={() => this.handleEnable(columnSetting)}
/>
))}
{additionalFieldOptions.count > 0 && (
<div>
{additionalFieldOptions.dimensions.map((dimension, index) => (
<ColumnItem
key={index}
title={dimension.displayName()}
onAdd={() => this.handleAddNewField(dimension.mbql())}
/>
))}
{additionalFieldOptions.fks.map((fk, index) => (
<div key={fk.id}>
<div className="my2 text-medium text-bold text-uppercase text-small">
{fk.name ||
(fk.field.target
? fk.field.target.table.display_name
: fk.field.display_name)}
</div>
{fk.dimensions.map((dimension, index) => (
<ColumnItem
key={index}
title={dimension.displayName()}
onAdd={() => this.handleAddNewField(dimension.mbql())}
/>
))}
</div>
))}
</div>
)}
</div>
);
}
}
......@@ -21,10 +21,12 @@ interface SortableColumnFunctions<T> {
onEnable?: (item: T) => void;
getItemName: (item: T) => string;
onColorChange?: (item: T, color: string) => void;
hideOnDisabled?: boolean;
}
interface SortableColumnProps<T> extends SortableColumnFunctions<T> {
item: T;
hideOnDisabled?: boolean;
}
const SortableColumn = SortableElement(function SortableColumn<
......@@ -38,7 +40,9 @@ const SortableColumn = SortableElement(function SortableColumn<
onAdd,
onEnable,
onColorChange,
hideOnDisabled,
}: SortableColumnProps<T>) {
const isHidden = !item.enabled && hideOnDisabled;
return (
<ColumnItem
title={getItemName(item)}
......@@ -56,6 +60,7 @@ const SortableColumn = SortableElement(function SortableColumn<
}
color={item.color}
draggable
isHidden={isHidden}
/>
);
}) as unknown as <T extends SortableItem>(
......@@ -77,6 +82,7 @@ const SortableColumnList = SortableContainer(function SortableColumnList<
onEnable,
onAdd,
onColorChange,
hideOnDisabled = false,
}: SortableColumnListProps<T>) {
return (
<div>
......@@ -91,6 +97,7 @@ const SortableColumnList = SortableContainer(function SortableColumnList<
onEnable={onEnable}
onAdd={onAdd}
onColorChange={onColorChange}
hideOnDisabled={hideOnDisabled}
/>
))}
</div>
......@@ -120,6 +127,7 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
getItemName,
items,
onColorChange,
hideOnDisabled,
}: ChartSettingOrderedItemsProps<T>) {
return (
<SortableColumnList
......@@ -134,6 +142,7 @@ export function ChartSettingOrderedItems<T extends SortableItem>({
onSortEnd={onSortEnd}
onColorChange={onColorChange}
distance={5}
hideOnDisabled={hideOnDisabled}
/>
);
}
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import Button from "metabase/core/components/Button";
export const ChartSettingOrderedSimpleRoot = styled.div`
padding-left: 1rem;
interface ChartSettingOrderedSimpleRootProps {
paddingLeft?: string;
}
export const ChartSettingOrderedSimpleRoot = styled.div<ChartSettingOrderedSimpleRootProps>`
padding-left: ${({ paddingLeft }) => paddingLeft || "1rem"};
padding-top: 0.5rem;
padding-bottom: 0.5rem;
`;
......@@ -17,3 +23,9 @@ export const ChartSettingMessage = styled.div`
font-weight: 700;
border-radius: 0.5rem;
`;
export const ExtraButton = styled(Button)`
position: absolute;
top: 0;
right: 0;
`;
......@@ -7,6 +7,7 @@ import { ChartSettingOrderedItems } from "./ChartSettingOrderedItems";
import {
ChartSettingMessage,
ChartSettingOrderedSimpleRoot,
ExtraButton,
} from "./ChartSettingOrderedSimple.styled";
interface SortableItem {
......@@ -19,21 +20,47 @@ interface SortableItem {
interface ChartSettingOrderedSimpleProps {
onChange: (rows: SortableItem[]) => void;
value: SortableItem[];
onShowWidget: (
widget: { props: { seriesKey: string } },
onShowPopoverWidget: (
widget: {
id?: string;
props?: { seriesKey?: string; initialKey?: string };
},
ref: HTMLElement | undefined,
) => void;
onSetCurrentWidget: (
widget: { props: { initialKey?: string } },
title: string,
) => void;
series: Series;
hasEditSettings: boolean;
hasOnEnable: boolean;
onChangeSeriesColor: (seriesKey: string, color: string) => void;
getItemTitle?: (item: SortableItem) => string;
getPopoverProps?: (item: SortableItem) => {
id?: string;
props?: {
seriesKey?: string;
initialKey?: string;
};
};
extraButton?: { text: string; key: string };
paddingLeft?: string;
hideOnDisabled: boolean;
}
export const ChartSettingOrderedSimple = ({
onChange,
value: orderedItems,
onShowWidget,
onShowPopoverWidget,
onSetCurrentWidget,
hasEditSettings = true,
hasOnEnable = true,
onChangeSeriesColor,
getItemTitle = (item: SortableItem) => item.name || "Unknown",
getPopoverProps = (item: SortableItem) => ({}),
extraButton,
paddingLeft,
hideOnDisabled = false,
}: ChartSettingOrderedSimpleProps) => {
const toggleDisplay = (selectedItem: SortableItem) => {
const index = orderedItems.findIndex(item => item.key === selectedItem.key);
......@@ -52,37 +79,46 @@ export const ChartSettingOrderedSimple = ({
onChange(itemsCopy);
};
const getItemTitle = (item: SortableItem) => {
return item.name || "Unknown";
};
const handleOnEdit = (item: SortableItem, ref: HTMLElement | undefined) => {
onShowWidget(
{
props: {
seriesKey: item.key,
},
},
ref,
);
onShowPopoverWidget(getPopoverProps(item), ref);
};
const handleColorChange = (item: SortableItem, color: string) => {
onChangeSeriesColor(item.key, color);
};
const handleExtra = () => {
if (extraButton) {
onSetCurrentWidget(
{
props: {
initialKey: extraButton.key,
},
},
extraButton.text,
);
}
};
return (
<ChartSettingOrderedSimpleRoot>
<ChartSettingOrderedSimpleRoot paddingLeft={paddingLeft}>
{extraButton && (
<ExtraButton onClick={handleExtra} onlyText>
{extraButton.text}
</ExtraButton>
)}
{orderedItems.length > 0 ? (
<ChartSettingOrderedItems
items={orderedItems}
getItemName={getItemTitle}
onRemove={toggleDisplay}
onEnable={toggleDisplay}
onRemove={hasOnEnable ? toggleDisplay : undefined}
onEnable={hasOnEnable ? toggleDisplay : undefined}
onSortEnd={handleSortEnd}
onEdit={hasEditSettings ? handleOnEdit : undefined}
onColorChange={handleColorChange}
distance={5}
hideOnDisabled={hideOnDisabled}
/>
) : (
<ChartSettingMessage>{t`Nothing to order`}</ChartSettingMessage>
......
......@@ -28,7 +28,10 @@ interface Props {
columns: DatasetColumn[];
question: Question;
onChange: (value: SettingValue) => void;
onShowWidget: (config: unknown, targetElement: HTMLElement | null) => void;
onShowPopoverWidget: (
config: unknown,
targetElement: HTMLElement | null,
) => void;
}
type ListColumnSlot = "left" | "right";
......@@ -50,7 +53,7 @@ function ChartSettingsListColumns({
value,
columns,
onChange,
onShowWidget,
onShowPopoverWidget,
}: Props) {
const onChangeColumn = useCallback(
(slot: ListColumnSlot, index: number, val: FieldIdOrFieldRef) => {
......@@ -74,7 +77,7 @@ function ChartSettingsListColumns({
_.isEqual(column.field_ref, fieldIdOrFieldRef),
);
if (column) {
onShowWidget(
onShowPopoverWidget(
{
id: "column_settings",
props: {
......@@ -85,7 +88,7 @@ function ChartSettingsListColumns({
);
}
},
[columns, onShowWidget],
[columns, onShowPopoverWidget],
);
const columnOptions = columns.map(column => ({
......
......@@ -32,6 +32,7 @@ const ColumnItem = ({
onColorChange,
draggable,
className = "",
isHidden,
}) => {
return (
<ColumnItemRoot
......@@ -39,6 +40,7 @@ const ColumnItem = ({
onClick={onClick}
isDraggable={draggable}
data-testid={`draggable-item-${title}`}
isHidden={isHidden}
>
<ColumnItemContainer>
{draggable && <ColumnItemDragHandle name="grabber2" size={12} />}
......
......@@ -6,12 +6,13 @@ import ChartSettingColorPicker from "./ChartSettingColorPicker";
interface ColumnItemRootProps {
isDraggable: boolean;
isHidden: boolean;
}
export const ColumnItemRoot = styled.div<ColumnItemRootProps>`
margin: 0.5rem 0;
overflow: hidden;
display: flex;
display: ${({ isHidden }) => (isHidden ? "none" : "flex")};
align-items: center;
border: 1px solid ${color("border")};
border-radius: 0.5rem;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment