Skip to content
Snippets Groups Projects
Unverified Commit 2e8e313a authored by Anton Kulyk's avatar Anton Kulyk Committed by GitHub
Browse files

Cherry-pick action creator component (#27673)


* Cherry-pick `ModelPicker`

* Cherry-pick actions editor and form components

* Migrate from entity forms

* Remove `ModelPicker`

* Temporarily add "New > Action" flow

* Disable save button if query is empty

* Fix utilities moved

* Fix type errors

* Address comments

* Simplify `ActionForm` tests

* Break down `ActionCreator` props

* Add basic `ActionCreator` tests

* update action creator header gap styling

* Extract `convertActionToQuestionCard`

* Fix `CreateActionForm` ignores action name

* Fix `FormModelPicker` crash

* Address comments

* Remove "New > Action" flow

Co-authored-by: default avatarRyan Laurie <iethree@gmail.com>
parent 03cf872f
No related branches found
No related tags found
No related merge requests found
import styled from "@emotion/styled";
import ItemPicker from "metabase/containers/ItemPicker";
export const MIN_POPOVER_WIDTH = 300;
export const PopoverItemPicker = styled(ItemPicker)<{ width: number }>`
width: ${({ width = MIN_POPOVER_WIDTH }) => width}px;
padding: 1rem;
overflow: auto;
`;
import React, {
useCallback,
useEffect,
useState,
useRef,
HTMLAttributes,
} from "react";
import { t } from "ttag";
import { useField } from "formik";
import { useUniqueId } from "metabase/hooks/use-unique-id";
import FormField from "metabase/core/components/FormField";
import SelectButton from "metabase/core/components/SelectButton";
import TippyPopoverWithTrigger from "metabase/components/PopoverWithTrigger/TippyPopoverWithTrigger";
import Models from "metabase/entities/questions";
import type { CardId } from "metabase-types/api";
import { PopoverItemPicker, MIN_POPOVER_WIDTH } from "./FormModelPicker.styled";
export interface FormModelPickerProps extends HTMLAttributes<HTMLDivElement> {
name: string;
title?: string;
placeholder?: string;
}
const ITEM_PICKER_MODELS = ["dataset"];
function FormModelPicker({
className,
style,
name,
title,
placeholder = t`Select a model`,
}: FormModelPickerProps) {
const id = useUniqueId();
const [{ value }, { error, touched }, { setValue }] = useField(name);
const formFieldRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(MIN_POPOVER_WIDTH);
useEffect(() => {
const { width: formFieldWidth } =
formFieldRef.current?.getBoundingClientRect() || {};
if (formFieldWidth) {
setWidth(formFieldWidth);
}
}, []);
const renderTrigger = useCallback(
({ onClick: handleShowPopover }) => (
<FormField
className={className}
style={style}
title={title}
htmlFor={id}
error={touched ? error : undefined}
ref={formFieldRef}
>
<SelectButton onClick={handleShowPopover}>
{typeof value === "number" ? <Models.Name id={value} /> : placeholder}
</SelectButton>
</FormField>
),
[id, value, title, placeholder, error, touched, className, style],
);
const renderContent = useCallback(
({ closePopover }) => {
return (
<PopoverItemPicker
value={{ id: value, model: "dataset" }}
models={ITEM_PICKER_MODELS}
onChange={({ id }: { id: CardId }) => {
setValue(id);
closePopover();
}}
showSearch
width={width}
/>
);
},
[value, width, setValue],
);
return (
<TippyPopoverWithTrigger
placement="bottom-start"
renderTrigger={renderTrigger}
popoverContent={renderContent}
maxWidth={width}
/>
);
}
export default FormModelPicker;
export { default } from "./FormModelPicker";
......@@ -2,12 +2,14 @@ import React from "react";
import Icon from "metabase/components/Icon";
import AccordionList from "metabase/core/components/AccordionList";
import type { Database } from "metabase-types/api/database";
import DataSelectorLoading from "../DataSelectorLoading";
import { RawDataBackButton } from "../DataSelector.styled";
import { checkDatabaseActionsEnabled } from "metabase/actions/utils";
import type { Database } from "metabase-types/api/database";
import type { Schema } from "../types";
import DataSelectorLoading from "../DataSelectorLoading";
import { RawDataBackButton } from "../DataSelector.styled";
type DataSelectorDatabasePickerProps = {
databases: Database[];
......@@ -16,6 +18,7 @@ type DataSelectorDatabasePickerProps = {
hasInitialFocus?: boolean;
hasNextStep?: boolean;
isLoading?: boolean;
requireWriteback?: boolean;
selectedDatabase?: Database;
selectedSchema?: Schema;
onBack?: () => void;
......@@ -27,6 +30,7 @@ type Item = {
database: Database;
index: number;
name: string;
writebackEnabled?: boolean;
};
type Section = {
......@@ -40,6 +44,7 @@ const DataSelectorDatabasePicker = ({
hasNextStep,
onBack,
hasInitialFocus,
requireWriteback = false,
}: DataSelectorDatabasePickerProps) => {
if (databases.length === 0) {
return <DataSelectorLoading />;
......@@ -77,6 +82,11 @@ const DataSelectorDatabasePicker = ({
sections={sections}
onChange={(item: Item) => onChangeDatabase(item.database)}
onChangeSection={handleChangeSection}
itemIsClickable={
requireWriteback
? (item: Item) => checkDatabaseActionsEnabled(item.database)
: undefined
}
itemIsSelected={(item: Item) =>
selectedDatabase && item.database.id === selectedDatabase.id
}
......
......@@ -15,7 +15,7 @@ const FRACTION_OF_TOTAL_VIEW_HEIGHT = 0.4;
// the query editor needs a fixed pixel height for now
// until we extract the resizable component
const FULL_HEIGHT = 500;
const FULL_HEIGHT = 400;
// This determines the max height that the editor *automatically* takes.
// - On load, long queries will be capped at this length
......
......@@ -55,4 +55,6 @@ function SidebarContent({
);
}
export default SidebarContent;
export default Object.assign(SidebarContent, {
Header: SidebarHeader,
});
......@@ -61,4 +61,4 @@ function SidebarHeader({ className, title, icon, onBack, onClose }: Props) {
);
}
export default SidebarHeader;
export default Object.assign(SidebarHeader, { Root: HeaderRoot });
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