Skip to content

Commit

Permalink
Merge pull request #5180 from gooddata/ach/semantic-search
Browse files Browse the repository at this point in the history
RELATED: CQ-582 next iteration for semantic search
  • Loading branch information
xMort authored Jul 30, 2024
2 parents 996800b + fe8ddc8 commit feae71f
Show file tree
Hide file tree
Showing 31 changed files with 414 additions and 169 deletions.
3 changes: 3 additions & 0 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions libs/sdk-backend-tiger/src/backend/features/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,13 @@ export function mapFeatures(features: FeaturesMap): Partial<ITigerFeatureFlags>
"BOOLEAN",
FeatureFlagsValues.enableWidgetIdentifiersRollout,
),
...loadFeature(
features,
TigerFeaturesNames.EnableAIFunctions,
"enableAIFunctions",
"BOOLEAN",
FeatureFlagsValues.enableAIFunctions,
),
};
}

Expand Down
4 changes: 4 additions & 0 deletions libs/sdk-backend-tiger/src/backend/uiFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export enum TigerFeaturesNames {
EnableSchedulingRollout = "enableSchedulingRollout",
EnableRollupTotals = "enableRollupTotals",
EnableWidgetIdentifiersRollout = "enableWidgetIdentifiersRollout",
EnableAIFunctions = "enableAIFunctions",
}

export type ITigerFeatureFlags = {
Expand Down Expand Up @@ -159,6 +160,7 @@ export type ITigerFeatureFlags = {
enableSchedulingRollout: typeof FeatureFlagsValues["enableSchedulingRollout"][number];
enableRollupTotals: typeof FeatureFlagsValues["enableRollupTotals"][number];
enableWidgetIdentifiersRollout: typeof FeatureFlagsValues["enableWidgetIdentifiersRollout"][number];
enableAIFunctions: typeof FeatureFlagsValues["enableAIFunctions"][number];
};

export const DefaultFeatureFlags: ITigerFeatureFlags = {
Expand Down Expand Up @@ -221,6 +223,7 @@ export const DefaultFeatureFlags: ITigerFeatureFlags = {
enableSchedulingRollout: false,
enableRollupTotals: false,
enableWidgetIdentifiersRollout: false,
enableAIFunctions: false,
};

export const FeatureFlagsValues = {
Expand Down Expand Up @@ -287,4 +290,5 @@ export const FeatureFlagsValues = {
enableSchedulingRollout: [true, false] as const,
enableRollupTotals: [true, false] as const,
enableWidgetIdentifiersRollout: [true, false] as const,
enableAIFunctions: [true, false] as const,
};
6 changes: 6 additions & 0 deletions libs/sdk-model/api/sdk-model.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2745,6 +2745,11 @@ export interface ISemanticSearchResultItem {
type: GenAISemanticSearchType;
}

// @alpha
export interface ISemanticSearchResultItemWithUrl extends ISemanticSearchResultItem {
url: string;
}

// @public
export interface ISeparators {
decimal: string;
Expand All @@ -2760,6 +2765,7 @@ export interface ISettings {
disableKpiDashboardHeadlineUnderline?: boolean;
enableAdDescriptionEdit?: boolean;
enableADMultipleDateFilters?: boolean;
enableAIFunctions?: boolean;
enableAlternativeDisplayFormSelection?: boolean;
enableAnalyticalDashboardPermissions?: boolean;
// (undocumented)
Expand Down
11 changes: 11 additions & 0 deletions libs/sdk-model/src/genAI/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ export interface ISemanticSearchResultItem {
description: string;
}

/**
* A single search result returned by semantic search, enriched with item URL.
* @alpha
*/
export interface ISemanticSearchResultItemWithUrl extends ISemanticSearchResultItem {
/**
* The UI URL of the found metadata object, i.e. deep link in KD, AD etc.
*/
url: string;
}

/**
* Type of the searchable object.
* @alpha
Expand Down
6 changes: 5 additions & 1 deletion libs/sdk-model/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,4 +921,8 @@ export {
isAutomationUserRecipient,
} from "./automations/index.js";

export { ISemanticSearchResultItem, GenAISemanticSearchType } from "./genAI/index.js";
export {
ISemanticSearchResultItem,
ISemanticSearchResultItemWithUrl,
GenAISemanticSearchType,
} from "./genAI/index.js";
4 changes: 4 additions & 0 deletions libs/sdk-model/src/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,10 @@ export interface ISettings {
enableSnowflakeKeyPairAuthentication?: boolean;
enableMotherDuckDataSource?: boolean;
enableSingleStoreDataSource?: boolean;
/**
* Enable GenAI-powered functionality, such as semantic-search
*/
enableAIFunctions?: boolean;

[key: string]: number | boolean | string | object | undefined;
}
Expand Down
5 changes: 1 addition & 4 deletions libs/sdk-ui-kit/src/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { HeaderAccount } from "./HeaderAccount.js";
import { HeaderMenu } from "./HeaderMenu.js";
import { HeaderUpsellButton } from "./HeaderUpsellButton.js";
import { HeaderInvite } from "./HeaderInvite.js";
import { HeaderSearch } from "./HeaderSearch.js";

function getOuterWidth(element: HTMLDivElement) {
const width = element.offsetWidth;
Expand Down Expand Up @@ -396,9 +395,7 @@ class AppHeaderCore extends Component<IAppHeaderProps & WrappedComponentProps, I

{this.renderTrialItems()}

{this.props.search ? (
<HeaderSearch className="gd-header-measure">{this.props.search}</HeaderSearch>
) : null}
{this.props.search}

{this.props.helpMenuItems.length ? (
<HeaderHelp
Expand Down
42 changes: 1 addition & 41 deletions libs/sdk-ui-kit/styles/scss/header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,6 @@ $button-normal-active-shadow: color.adjust($button-normal-active-border-color, $

.gd-header-invite,
.gd-header-account,
.gd-header-search,
.gd-header-help {
height: 100%;
margin: 0;
Expand Down Expand Up @@ -590,44 +589,6 @@ $button-normal-active-shadow: color.adjust($button-normal-active-border-color, $
}
}

.gd-header-search {
position: relative;
box-sizing: border-box;
padding-right: 34px;

&:hover {
background: rgba(255, 255, 255, 0.3);
}

&::after {
content: "\e612";
position: absolute;
top: 0;
right: 11px;
margin-left: 11px;
width: 12px;
opacity: 0.5;
text-align: center;
font-family: variables.$gd-font-indigo;
font-size: 18px;
font-weight: 700;
}

&.is-open::after {
content: "\e613";
}
}

.gd-icon-header-search {
vertical-align: middle;
opacity: 0.8;
margin-right: 6px;

&::before {
content: "\e62b";
}
}

.gd-header-username {
&::before {
margin-right: 8px;
Expand Down Expand Up @@ -772,8 +733,7 @@ $button-normal-active-shadow: color.adjust($button-normal-active-border-color, $
}
}

.gd-header-account-dropdown,
.gd-header-search-dropdown {
.gd-header-account-dropdown {
overflow: hidden;

.gd-list {
Expand Down
5 changes: 3 additions & 2 deletions libs/sdk-ui-semantic-search/api/sdk-ui-semantic-search.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { GenAISemanticSearchType } from '@gooddata/sdk-model';
import { IAnalyticalBackend } from '@gooddata/sdk-backend-spi';
import { ISemanticSearchResultItem } from '@gooddata/sdk-model';
import { ISemanticSearchResultItemWithUrl } from '@gooddata/sdk-model';
import * as React_2 from 'react';

// @alpha
Expand Down Expand Up @@ -39,7 +40,7 @@ export type SemanticSearchHookInput = {
export type SemanticSearchInputResult = {
searchStatus: "idle" | "loading" | "error" | "success";
searchError: string;
searchResults: ISemanticSearchResultItem[];
searchResults: ISemanticSearchResultItemWithUrl[];
};

// @alpha
Expand All @@ -48,7 +49,7 @@ export type SemanticSearchProps = SemanticSearchCoreProps & {
};

// @internal
export const useListSelector: <T>(items: T[], onSelect: (item: T) => void) => [number, (index: number) => void];
export const useListSelector: <T>(items: T[], onSelect: (item: T) => void) => [T, (item: T) => void];

// @alpha
export const useSemanticSearch: ({ searchTerm, objectTypes, deepSearch, limit, backend, workspace, }: SemanticSearchHookInput) => SemanticSearchInputResult;
Expand Down
3 changes: 2 additions & 1 deletion libs/sdk-ui-semantic-search/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"@gooddata/sdk-ui": "workspace:*",
"@gooddata/sdk-ui-kit": "workspace:*",
"@gooddata/sdk-backend-spi": "workspace:*",
"classnames": "^2.3.1"
"classnames": "^2.3.1",
"@gooddata/sdk-ui-theme-provider": "workspace:*"
},
"peerDependencies": {
"react": "^16.10.0 || ^17.0.0 || ^18.0.0",
Expand Down
71 changes: 53 additions & 18 deletions libs/sdk-ui-semantic-search/src/ResultsItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import * as React from "react";
import classnames from "classnames";
import { ISemanticSearchResultItem } from "@gooddata/sdk-model";
import { Icon, Typography } from "@gooddata/sdk-ui-kit";
import { ISemanticSearchResultItemWithUrl } from "@gooddata/sdk-model";
import { Bubble, BubbleHoverTrigger, EllipsisText, Icon, Typography } from "@gooddata/sdk-ui-kit";
import { useTheme } from "@gooddata/sdk-ui-theme-provider";
import { FormattedMessage } from "react-intl";

export const ITEM_HEIGHT = 45;
const BUBBLE_ALIGN_POINTS = [{ align: "bc tr", offset: { x: 2, y: -4 } }];

/**
* Props for the ResultsItem component.
Expand All @@ -15,7 +18,7 @@ export type ResultsItemProps = {
/**
* The item to render.
*/
item: ISemanticSearchResultItem;
item: ISemanticSearchResultItemWithUrl;
/**
* The height of the item.
*/
Expand All @@ -27,52 +30,84 @@ export type ResultsItemProps = {
/**
* Whether the item is selected.
*/
selected: boolean;
active: boolean;
/**
* Callback to trigger when the item is hovered.
*/
onHover: () => void;
onHover: (item: ISemanticSearchResultItemWithUrl) => void;
/**
* Callback to trigger when the item is clicked.
*/
onClick: () => void;
onSelect: (item: ISemanticSearchResultItemWithUrl) => void;
};

/**
* A single result item in the search results.
* @internal
*/
export const ResultsItem: React.FC<ResultsItemProps> = ({ item, onHover, onClick, selected }) => {
const onMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
export const ResultsItem: React.FC<ResultsItemProps> = ({ item, onHover, onSelect, active }) => {
const theme = useTheme();
const onMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
// Prevent multiple triggers
if (e.target !== e.currentTarget) {
return;
}

onHover();
onHover(item);
};

const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
onSelect(item);
};

const className = classnames({
"gd-semantic-search__results-item": true,
"gd-semantic-search__results-item--selected": selected,
"gd-semantic-search__results-item--active": active,
});

// Use mouse enter with the target check instead of hover to prevent multiple triggers
// Use mouse down instead of click to prevent the dialog from disappearing before the click event is caught
return (
<div className={className} onMouseEnter={onMouseEnter} onMouseDown={onClick}>
<div className="gd-semantic-search__results-item__icon">{renderItemIcon(item)}</div>
<div className="gd-semantic-search__results-item__text">
<Typography tagName="p">{item.title}</Typography>
</div>
</div>
<a className={className} onMouseEnter={onMouseEnter} onClick={onClick} href={item.url}>
<span className="gd-semantic-search__results-item__icon">{renderItemIcon(item)}</span>
<span className="gd-semantic-search__results-item__text">{item.title}</span>
<span className="gd-semantic-search__results-item__details">
<BubbleHoverTrigger eventsOnBubble>
<Icon.QuestionMark color={theme?.palette?.complementary?.c7 ?? "#6D7680"} />
<Bubble
className="bubble-light gd-semantic-search__bubble"
alignPoints={BUBBLE_ALIGN_POINTS}
arrowStyle={{ display: "none" }}
>
{/* It's OK to have div inline, as this chunk is rendered through portal */}
<div className="gd-semantic-search__results-item__details__contents">
<Typography tagName="h3">{item.title}</Typography>
{renderIfHasDescription(item)}
<FormattedMessage tagName="h4" id="semantic-search.id" />
<Typography tagName={"p"}>{item.id}</Typography>
</div>
</Bubble>
</BubbleHoverTrigger>
</span>
</a>
);
};

/**
* Render the description if it is different from the title.
*/
const renderIfHasDescription = (item: ISemanticSearchResultItemWithUrl) => {
if (item.description && item.description !== item.title) {
return <EllipsisText maxLines={7} text={item.description} />;
}

return null;
};

/**
* Pick an icon according to the item type.
*/
const renderItemIcon = (item: ISemanticSearchResultItem) => {
const renderItemIcon = (item: ISemanticSearchResultItemWithUrl) => {
switch (item.type) {
case "dashboard":
return <Icon.Dashboard />;
Expand Down
Loading

0 comments on commit feae71f

Please sign in to comment.