Skip to content
Snippets Groups Projects
Unverified Commit 185389ba authored by github-automation-metabase's avatar github-automation-metabase Committed by GitHub
Browse files

fix(sdk): Split `useSummarizeQuery` into specialized hooks (#49841) (#50029)


Co-authored-by: default avatarOisin Coveney <oisin@metabase.com>
parent 121e301a
No related branches found
No related tags found
No related merge requests found
Showing
with 244 additions and 228 deletions
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { t } from "ttag"; import { t } from "ttag";
import { useInteractiveQuestionContext } from "embedding-sdk/components/private/InteractiveQuestion/context";
import CS from "metabase/css/core/index.css"; import CS from "metabase/css/core/index.css";
import { import {
SummarizeAggregationItemList, SummarizeAggregationItemList,
SummarizeBreakoutColumnList, SummarizeBreakoutColumnList,
useSummarizeQuery,
} from "metabase/query_builder/components/view/sidebars/SummarizeSidebar/SummarizeContent"; } from "metabase/query_builder/components/view/sidebars/SummarizeSidebar/SummarizeContent";
import { Button, Divider, Group, Stack } from "metabase/ui"; import { Button, Divider, Group, Stack } from "metabase/ui";
import type * as Lib from "metabase-lib"; import * as Lib from "metabase-lib";
import type Question from "metabase-lib/v1/Question"; import type Question from "metabase-lib/v1/Question";
import { useInteractiveQuestionContext } from "../context";
type SummarizeProps = { type SummarizeProps = {
onClose: () => void; onClose: () => void;
}; };
...@@ -38,8 +38,11 @@ const SummarizeInner = ({ ...@@ -38,8 +38,11 @@ const SummarizeInner = ({
const [currentQuery, setCurrentQuery] = useState<Lib.Query>(question.query()); const [currentQuery, setCurrentQuery] = useState<Lib.Query>(question.query());
// yeah we need to change this
const stageIndex = Lib.stageCount(currentQuery);
const onApplyFilter = () => { const onApplyFilter = () => {
if (query) { if (currentQuery) {
onQueryChange(currentQuery); onQueryChange(currentQuery);
onClose(); onClose();
} }
...@@ -52,39 +55,22 @@ const SummarizeInner = ({ ...@@ -52,39 +55,22 @@ const SummarizeInner = ({
onClose(); onClose();
}; };
const { const hasAggregations = Lib.aggregations(currentQuery, stageIndex).length > 0;
query,
stageIndex,
aggregations,
handleAddBreakout,
handleQueryChange,
handleRemoveBreakout,
handleReplaceBreakouts,
handleUpdateBreakout,
hasAggregations,
} = useSummarizeQuery({
query: currentQuery,
onQueryChange: setCurrentQuery,
});
return ( return (
<Stack className={CS.overflowHidden} h="100%" w="100%"> <Stack className={CS.overflowHidden} h="100%" w="100%">
<Stack className={CS.overflowYScroll}> <Stack className={CS.overflowYScroll}>
<SummarizeAggregationItemList <SummarizeAggregationItemList
query={query} query={currentQuery}
onQueryChange={setCurrentQuery}
stageIndex={stageIndex} stageIndex={stageIndex}
aggregations={aggregations}
onQueryChange={handleQueryChange}
/> />
<Divider my="lg" /> <Divider my="lg" />
{hasAggregations && ( {hasAggregations && (
<SummarizeBreakoutColumnList <SummarizeBreakoutColumnList
query={query} query={currentQuery}
onQueryChange={setCurrentQuery}
stageIndex={stageIndex} stageIndex={stageIndex}
onAddBreakout={handleAddBreakout}
onUpdateBreakout={handleUpdateBreakout}
onRemoveBreakout={handleRemoveBreakout}
onReplaceBreakouts={handleReplaceBreakouts}
/> />
)} )}
</Stack> </Stack>
......
...@@ -49,6 +49,7 @@ export const StructuredQueryRightSidebar = ({ ...@@ -49,6 +49,7 @@ export const StructuredQueryRightSidebar = ({
}); });
}} }}
onClose={onCloseSummary} onClose={onCloseSummary}
stageIndex={-1}
/> />
), ),
) )
......
import { useCallback, useState } from "react"; import { useDisclosure } from "@mantine/hooks";
import { AggregationPicker } from "metabase/common/components/AggregationPicker"; import { AggregationPicker } from "metabase/common/components/AggregationPicker";
import { Popover } from "metabase/ui"; import { Popover } from "metabase/ui";
import * as Lib from "metabase-lib"; import type * as Lib from "metabase-lib";
import { AggregationName, RemoveIcon, Root } from "./AggregationItem.styled"; import { AggregationName, RemoveIcon, Root } from "./AggregationItem.styled";
interface AggregationItemProps { interface AggregationItemProps {
query: Lib.Query; query: Lib.Query;
onQueryChange: (query: Lib.Query) => void;
stageIndex: number; stageIndex: number;
aggregation: Lib.AggregationClause; aggregation: Lib.AggregationClause;
aggregationIndex: number; aggregationIndex: number;
onQueryChange: (query: Lib.Query) => void; displayName: string;
onAggregationRemove: () => void;
operators: Lib.AggregationOperator[];
} }
export function AggregationItem({ export function AggregationItem({
...@@ -20,30 +23,22 @@ export function AggregationItem({ ...@@ -20,30 +23,22 @@ export function AggregationItem({
aggregation, aggregation,
aggregationIndex, aggregationIndex,
onQueryChange, onQueryChange,
displayName,
onAggregationRemove,
operators,
}: AggregationItemProps) { }: AggregationItemProps) {
const [isOpened, setIsOpened] = useState(false); const [isOpened, { toggle }] = useDisclosure(false);
const { displayName } = Lib.displayInfo(query, stageIndex, aggregation);
const operators = Lib.selectedAggregationOperators(
Lib.availableAggregationOperators(query, stageIndex),
aggregation,
);
const handleRemove = useCallback(() => {
const nextQuery = Lib.removeClause(query, stageIndex, aggregation);
onQueryChange(nextQuery);
}, [query, stageIndex, aggregation, onQueryChange]);
return ( return (
<Popover opened={isOpened} onChange={setIsOpened}> <Popover opened={isOpened} onChange={toggle}>
<Popover.Target> <Popover.Target>
<Root <Root
aria-label={displayName} aria-label={displayName}
data-testid="aggregation-item" data-testid="aggregation-item"
onClick={() => setIsOpened(!isOpened)} onClick={toggle}
> >
<AggregationName>{displayName}</AggregationName> <AggregationName>{displayName}</AggregationName>
<RemoveIcon name="close" onClick={handleRemove} /> <RemoveIcon name="close" onClick={onAggregationRemove} />
</Root> </Root>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
......
...@@ -4,32 +4,28 @@ import { t } from "ttag"; ...@@ -4,32 +4,28 @@ import { t } from "ttag";
import Input from "metabase/core/components/Input"; import Input from "metabase/core/components/Input";
import { useDebouncedValue } from "metabase/hooks/use-debounced-value"; import { useDebouncedValue } from "metabase/hooks/use-debounced-value";
import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants"; import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants";
import type { UpdateQueryHookProps } from "metabase/query_builder/hooks/types";
import { useBreakoutQueryHandlers } from "metabase/query_builder/hooks/use-breakout-query-handlers";
import { DelayGroup } from "metabase/ui"; import { DelayGroup } from "metabase/ui";
import * as Lib from "metabase-lib"; import * as Lib from "metabase-lib";
import { ColumnGroupName, SearchContainer } from "./BreakoutColumnList.styled"; import { ColumnGroupName, SearchContainer } from "./BreakoutColumnList.styled";
import { BreakoutColumnListItem } from "./BreakoutColumnListItem"; import { BreakoutColumnListItem } from "./BreakoutColumnListItem";
export interface BreakoutColumnListProps { export type BreakoutColumnListProps = UpdateQueryHookProps;
query: Lib.Query;
stageIndex: number;
onAddBreakout: (column: Lib.ColumnMetadata) => void;
onUpdateBreakout: (
breakout: Lib.BreakoutClause,
column: Lib.ColumnMetadata,
) => void;
onRemoveBreakout: (breakout: Lib.BreakoutClause) => void;
onReplaceBreakouts: (column: Lib.ColumnMetadata) => void;
}
export function BreakoutColumnList({ export function BreakoutColumnList({
query, query,
stageIndex, onQueryChange,
onAddBreakout, stageIndex = -1,
onUpdateBreakout,
onRemoveBreakout,
onReplaceBreakouts,
}: BreakoutColumnListProps) { }: BreakoutColumnListProps) {
const {
onAddBreakout,
onUpdateBreakout,
onRemoveBreakout,
onReplaceBreakouts,
} = useBreakoutQueryHandlers({ query, onQueryChange, stageIndex });
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const debouncedSearchQuery = useDebouncedValue( const debouncedSearchQuery = useDebouncedValue(
searchQuery, searchQuery,
......
import { useMemo } from "react";
import type { UpdateQueryHookProps } from "metabase/query_builder/hooks/types";
import { getAggregationItems } from "metabase/query_builder/utils/get-aggregation-items";
import { Group, type GroupProps } from "metabase/ui"; import { Group, type GroupProps } from "metabase/ui";
import type * as Lib from "metabase-lib"; import * as Lib from "metabase-lib";
import { AddAggregationButton } from "../AddAggregationButton"; import { AddAggregationButton } from "../AddAggregationButton";
import { AggregationItem } from "../AggregationItem"; import { AggregationItem } from "../AggregationItem";
type SummarizeAggregationItemListProps = { type SummarizeAggregationItemListProps = UpdateQueryHookProps & GroupProps;
query: Lib.Query;
stageIndex: number;
aggregations: Lib.AggregationClause[];
onQueryChange: (query: Lib.Query) => void;
} & GroupProps;
export const SummarizeAggregationItemList = ({ export const SummarizeAggregationItemList = ({
query, query,
stageIndex,
aggregations,
onQueryChange, onQueryChange,
stageIndex,
...containerProps ...containerProps
}: SummarizeAggregationItemListProps) => ( }: SummarizeAggregationItemListProps) => {
<Group const aggregationItems = useMemo(
data-testid="summarize-aggregation-item-list" () => getAggregationItems({ query, stageIndex }),
spacing="sm" [query, stageIndex],
align="flex-start" );
{...containerProps}
> const handleRemove = (aggregation: Lib.AggregationClause) => {
{aggregations.map((aggregation, aggregationIndex) => ( const nextQuery = Lib.removeClause(query, stageIndex, aggregation);
<AggregationItem onQueryChange(nextQuery);
key={aggregationIndex} };
return (
<Group
data-testid="summarize-aggregation-item-list"
spacing="sm"
align="flex-start"
{...containerProps}
>
{aggregationItems.map(
({ aggregation, displayName, aggregationIndex, operators }) => (
<AggregationItem
key={aggregationIndex}
query={query}
stageIndex={stageIndex}
aggregation={aggregation}
aggregationIndex={aggregationIndex}
onQueryChange={onQueryChange}
displayName={displayName}
onAggregationRemove={() => handleRemove(aggregation)}
operators={operators}
/>
),
)}
<AddAggregationButton
query={query} query={query}
stageIndex={stageIndex} stageIndex={stageIndex}
aggregation={aggregation}
aggregationIndex={aggregationIndex}
onQueryChange={onQueryChange} onQueryChange={onQueryChange}
/> />
))} </Group>
<AddAggregationButton );
query={query} };
stageIndex={stageIndex}
onQueryChange={onQueryChange}
/>
</Group>
);
import { t } from "ttag"; import { t } from "ttag";
import type { UpdateQueryHookProps } from "metabase/query_builder/hooks/types";
import { Space, Stack, type StackProps, Title } from "metabase/ui"; import { Space, Stack, type StackProps, Title } from "metabase/ui";
import type * as Lib from "metabase-lib";
import { BreakoutColumnList } from "../BreakoutColumnList"; import { BreakoutColumnList } from "../BreakoutColumnList";
type SummarizeBreakoutColumnListProps = { type SummarizeBreakoutColumnListProps = UpdateQueryHookProps & StackProps;
query: Lib.Query;
stageIndex: number;
onAddBreakout: (column: Lib.ColumnMetadata) => void;
onUpdateBreakout: (
clause: Lib.BreakoutClause,
column: Lib.ColumnMetadata,
) => void;
onRemoveBreakout: (clause: Lib.BreakoutClause) => void;
onReplaceBreakouts: (column: Lib.ColumnMetadata) => void;
} & StackProps;
export const SummarizeBreakoutColumnList = ({ export const SummarizeBreakoutColumnList = ({
query, query,
onQueryChange,
stageIndex, stageIndex,
onAddBreakout,
onUpdateBreakout,
onRemoveBreakout,
onReplaceBreakouts,
...containerProps ...containerProps
}: SummarizeBreakoutColumnListProps) => ( }: SummarizeBreakoutColumnListProps) => (
<Stack <Stack
...@@ -36,11 +23,8 @@ export const SummarizeBreakoutColumnList = ({ ...@@ -36,11 +23,8 @@ export const SummarizeBreakoutColumnList = ({
<Space my="sm" /> <Space my="sm" />
<BreakoutColumnList <BreakoutColumnList
query={query} query={query}
onQueryChange={onQueryChange}
stageIndex={stageIndex} stageIndex={stageIndex}
onAddBreakout={onAddBreakout}
onUpdateBreakout={onUpdateBreakout}
onRemoveBreakout={onRemoveBreakout}
onReplaceBreakouts={onReplaceBreakouts}
/> />
</Stack> </Stack>
); );
export * from "./use-summarize-query";
export * from "./SummarizeAggregationItemList"; export * from "./SummarizeAggregationItemList";
export * from "./SummarizeBreakoutColumnList"; export * from "./SummarizeBreakoutColumnList";
import { useCallback, useMemo, useState } from "react";
import * as Lib from "metabase-lib";
const STAGE_INDEX = -1;
interface UseSummarizeQueryProps {
query: Lib.Query;
onQueryChange: (nextQuery: Lib.Query) => void;
}
export const useSummarizeQuery = ({
query: initialQuery,
onQueryChange,
}: UseSummarizeQueryProps) => {
const [hasDefaultAggregation, setHasDefaultAggregation] = useState(() =>
shouldAddDefaultAggregation(initialQuery),
);
const query = useMemo(
() =>
hasDefaultAggregation
? Lib.aggregateByCount(initialQuery, STAGE_INDEX)
: initialQuery,
[initialQuery, hasDefaultAggregation],
);
const aggregations = Lib.aggregations(query, STAGE_INDEX);
const hasAggregations = aggregations.length > 0;
const handleChange = useCallback(
(nextQuery: Lib.Query) => {
setHasDefaultAggregation(false);
onQueryChange(nextQuery);
},
[onQueryChange],
);
const handleQueryChange = useCallback(
(nextQuery: Lib.Query) => {
const newAggregations = Lib.aggregations(nextQuery, STAGE_INDEX);
if (hasDefaultAggregation && newAggregations.length === 0) {
setHasDefaultAggregation(false);
} else {
handleChange(nextQuery);
}
},
[handleChange, hasDefaultAggregation],
);
const handleAddBreakout = useCallback(
(column: Lib.ColumnMetadata) => {
const nextQuery = Lib.breakout(query, STAGE_INDEX, column);
handleChange(nextQuery);
},
[query, handleChange],
);
const handleUpdateBreakout = useCallback(
(clause: Lib.BreakoutClause, column: Lib.ColumnMetadata) => {
const nextQuery = Lib.replaceClause(query, STAGE_INDEX, clause, column);
handleChange(nextQuery);
},
[query, handleChange],
);
const handleRemoveBreakout = useCallback(
(clause: Lib.BreakoutClause) => {
const nextQuery = Lib.removeClause(query, STAGE_INDEX, clause);
handleChange(nextQuery);
},
[query, handleChange],
);
const handleReplaceBreakouts = useCallback(
(column: Lib.ColumnMetadata) => {
const nextQuery = Lib.replaceBreakouts(query, STAGE_INDEX, column);
handleChange(nextQuery);
},
[query, handleChange],
);
return {
query,
stageIndex: STAGE_INDEX,
aggregations,
hasAggregations,
handleQueryChange,
handleAddBreakout,
handleUpdateBreakout,
handleRemoveBreakout,
handleReplaceBreakouts,
};
};
function shouldAddDefaultAggregation(query: Lib.Query): boolean {
const aggregations = Lib.aggregations(query, STAGE_INDEX);
return aggregations.length === 0;
}
...@@ -2,42 +2,37 @@ import { useCallback } from "react"; ...@@ -2,42 +2,37 @@ import { useCallback } from "react";
import { t } from "ttag"; import { t } from "ttag";
import { color } from "metabase/lib/colors"; import { color } from "metabase/lib/colors";
import type { UpdateQueryHookProps } from "metabase/query_builder/hooks/types";
import { useDefaultQueryAggregation } from "metabase/query_builder/hooks/use-default-query-aggregation";
import { Divider } from "metabase/ui"; import { Divider } from "metabase/ui";
import type * as Lib from "metabase-lib";
import { import {
SummarizeAggregationItemList, SummarizeAggregationItemList,
SummarizeBreakoutColumnList, SummarizeBreakoutColumnList,
useSummarizeQuery,
} from "./SummarizeContent"; } from "./SummarizeContent";
import { SidebarView } from "./SummarizeSidebar.styled"; import { SidebarView } from "./SummarizeSidebar.styled";
interface SummarizeSidebarProps { type SummarizeSidebarProps = {
className?: string; className?: string;
query: Lib.Query;
onQueryChange: (query: Lib.Query) => void;
onClose: () => void; onClose: () => void;
} } & UpdateQueryHookProps;
export function SummarizeSidebar({ export function SummarizeSidebar({
className, className,
query: initialQuery, query: initialQuery,
onQueryChange, onQueryChange,
onClose, onClose,
stageIndex,
}: SummarizeSidebarProps) { }: SummarizeSidebarProps) {
const { const {
query, query,
stageIndex, onUpdateQuery: onDefaultQueryChange,
aggregations, onAggregationChange,
hasAggregations, hasAggregations,
handleQueryChange, } = useDefaultQueryAggregation({
handleAddBreakout,
handleUpdateBreakout,
handleRemoveBreakout,
handleReplaceBreakouts,
} = useSummarizeQuery({
query: initialQuery, query: initialQuery,
onQueryChange, onQueryChange,
stageIndex,
}); });
const handleDoneClick = useCallback(() => { const handleDoneClick = useCallback(() => {
...@@ -55,20 +50,16 @@ export function SummarizeSidebar({ ...@@ -55,20 +50,16 @@ export function SummarizeSidebar({
<SummarizeAggregationItemList <SummarizeAggregationItemList
px="lg" px="lg"
query={query} query={query}
onQueryChange={onAggregationChange}
stageIndex={stageIndex} stageIndex={stageIndex}
aggregations={aggregations}
onQueryChange={handleQueryChange}
/> />
<Divider my="lg" /> <Divider my="lg" />
{hasAggregations && ( {hasAggregations && (
<SummarizeBreakoutColumnList <SummarizeBreakoutColumnList
px="lg" px="lg"
query={query} query={query}
onQueryChange={onDefaultQueryChange}
stageIndex={stageIndex} stageIndex={stageIndex}
onAddBreakout={handleAddBreakout}
onUpdateBreakout={handleUpdateBreakout}
onRemoveBreakout={handleRemoveBreakout}
onReplaceBreakouts={handleReplaceBreakouts}
/> />
)} )}
</SidebarView> </SidebarView>
......
...@@ -72,6 +72,7 @@ async function setup({ ...@@ -72,6 +72,7 @@ async function setup({
onQueryChange(nextQuery); onQueryChange(nextQuery);
}} }}
onClose={onClose} onClose={onClose}
stageIndex={-1}
/> />
); );
} }
......
import type * as Lib from "metabase-lib";
export type UpdateQueryHookProps = {
query: Lib.Query;
onQueryChange: (nextQuery: Lib.Query) => void;
stageIndex: number;
};
import { useCallback } from "react";
import * as Lib from "metabase-lib";
import type { UpdateQueryHookProps } from "./types";
export const useBreakoutQueryHandlers = ({
query,
onQueryChange,
stageIndex,
}: UpdateQueryHookProps) => {
const onAddBreakout = useCallback(
(column: Lib.ColumnMetadata) => {
const nextQuery = Lib.breakout(query, stageIndex, column);
onQueryChange(nextQuery);
},
[query, stageIndex, onQueryChange],
);
const onUpdateBreakout = useCallback(
(clause: Lib.BreakoutClause, column: Lib.ColumnMetadata) => {
const nextQuery = Lib.replaceClause(query, stageIndex, clause, column);
onQueryChange(nextQuery);
},
[query, stageIndex, onQueryChange],
);
const onRemoveBreakout = useCallback(
(clause: Lib.BreakoutClause) => {
const nextQuery = Lib.removeClause(query, stageIndex, clause);
onQueryChange(nextQuery);
},
[query, stageIndex, onQueryChange],
);
const onReplaceBreakouts = useCallback(
(column: Lib.ColumnMetadata) => {
const nextQuery = Lib.replaceBreakouts(query, stageIndex, column);
onQueryChange(nextQuery);
},
[query, stageIndex, onQueryChange],
);
return {
onAddBreakout,
onUpdateBreakout,
onRemoveBreakout,
onReplaceBreakouts,
};
};
import { useCallback, useMemo, useState } from "react";
import * as Lib from "metabase-lib";
import type { UpdateQueryHookProps } from "./types";
export const useDefaultQueryAggregation = ({
query: initialQuery,
onQueryChange,
stageIndex,
}: UpdateQueryHookProps) => {
const [hasDefaultAggregation, setHasDefaultAggregation] = useState(() =>
shouldAddDefaultAggregation(initialQuery, stageIndex),
);
const query = useMemo(
() =>
hasDefaultAggregation
? Lib.aggregateByCount(initialQuery, stageIndex)
: initialQuery,
[hasDefaultAggregation, initialQuery, stageIndex],
);
const hasAggregations = useMemo(
() => Lib.aggregations(query, stageIndex).length > 0,
[query, stageIndex],
);
const onUpdateQuery = useCallback(
(nextQuery: Lib.Query) => {
setHasDefaultAggregation(false);
onQueryChange(nextQuery);
},
[onQueryChange],
);
const onAggregationChange = useCallback(
(nextQuery: Lib.Query) => {
const newAggregations = Lib.aggregations(nextQuery, stageIndex);
setHasDefaultAggregation(false);
if (!hasDefaultAggregation || newAggregations.length !== 0) {
onQueryChange(nextQuery);
}
},
[hasDefaultAggregation, onQueryChange, stageIndex],
);
return {
query,
hasAggregations,
onUpdateQuery,
onAggregationChange,
};
};
function shouldAddDefaultAggregation(
query: Lib.Query,
stageIndex = -1,
): boolean {
const aggregations = Lib.aggregations(query, stageIndex);
return aggregations.length === 0;
}
import * as Lib from "metabase-lib";
import type { UpdateQueryHookProps } from "../hooks/types";
export const getAggregationItems = ({
query,
stageIndex,
}: Pick<UpdateQueryHookProps, "query" | "stageIndex">) => {
const aggregations = Lib.aggregations(query, stageIndex);
return aggregations.map((aggregation, aggregationIndex) => {
const { displayName } = Lib.displayInfo(query, stageIndex, aggregation);
const operators = Lib.selectedAggregationOperators(
Lib.availableAggregationOperators(query, stageIndex),
aggregation,
);
return {
aggregation,
aggregationIndex,
operators,
displayName,
};
});
};
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