diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index 99156677d5..80017d7e43 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -1339,6 +1339,9 @@ export const PROPERTY_PANE_EMPTY_SEARCH_RESULT_MESSAGE =
export const PROPERTY_SEARCH_INPUT_PLACEHOLDER =
"Search for controls, labels etc";
export const EXPLORER_BETA_ENTITY = () => "BETA";
+export const BINDING_WIDGET_WALKTHROUGH_TITLE = () => "Widget properties";
+export const BINDING_WIDGET_WALKTHROUGH_DESC = () =>
+ `We’ve set the table data property for you. You can change it at anytime. The properties pane is a central hub for configuring widgets, allowing you to easily modify settings.`;
// API Pane
export const API_PANE_NO_BODY = () => "This request does not have a body";
diff --git a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx
index 5343a217ec..1c7ba41894 100644
--- a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx
+++ b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx
@@ -47,11 +47,14 @@ import textWidgetIconSvg from "../../../widgets/TextWidget/icon.svg";
import listWidgetIconSvg from "../../../widgets/ListWidget/icon.svg";
import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
import {
- getFeatureFlagShownStatus,
+ getFeatureWalkthroughShown,
isUserSignedUpFlagSet,
- setFeatureFlagShownStatus,
+ setFeatureWalkthroughShown,
} from "utils/storage";
import { getCurrentUser } from "selectors/usersSelectors";
+import localStorage from "utils/localStorage";
+import { WIDGET_ID_SHOW_WALKTHROUGH } from "constants/WidgetConstants";
+import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
const BINDING_GUIDE_GIF = `${ASSETS_CDN_URL}/binding.gif`;
@@ -362,14 +365,15 @@ function SuggestedWidgets(props: SuggestedWidgetProps) {
queryId?: string;
}>();
- const closeWalkthrough = async () => {
- if (isWalkthroughOpened) {
- popFeature && popFeature();
- await setFeatureFlagShownStatus(FEATURE_FLAG.ab_ds_binding_enabled, true);
- }
+ const closeWalkthrough = () => {
+ popFeature && popFeature("BINDING_WIDGET");
+ setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_binding_enabled,
+ true,
+ );
};
- const addWidget = (
+ const addWidget = async (
suggestedWidget: SuggestedWidget,
widgetInfo: WidgetBindingInfo,
) => {
@@ -388,16 +392,25 @@ function SuggestedWidgets(props: SuggestedWidgetProps) {
AnalyticsUtil.logEvent("SUGGESTED_WIDGET_CLICK", {
widget: suggestedWidget.type,
[AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
- FEATURE_FLAG.ab_ds_binding_enabled,
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_binding_enabled,
[AB_TESTING_EVENT_KEYS.abTestingFlagValue]: isEnabledForQueryBinding,
isWalkthroughOpened,
});
- closeWalkthrough();
+ const showStatus = await getFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.binding_widget,
+ );
+ // To enable setting the widget id for showing walkthrough once the widget is created in WidgetOperationSagas.tsx -> addSuggestedWidget function
+ if (!showStatus && isEnabledForQueryBinding) {
+ (payload.props as any).setWidgetIdForWalkthrough = "true";
+ }
+ if (isWalkthroughOpened) {
+ closeWalkthrough();
+ }
dispatch(addSuggestedWidget(payload));
};
- const handleBindData = (widgetId: string) => {
+ const handleBindData = async (widgetId: string) => {
dispatch(
bindDataOnCanvas({
queryId: (params.apiId || params.queryId) as string,
@@ -406,12 +419,24 @@ function SuggestedWidgets(props: SuggestedWidgetProps) {
}),
);
- closeWalkthrough();
+ if (isEnabledForQueryBinding) {
+ const value = await getFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.binding_widget,
+ );
+ if (!value) {
+ localStorage.setItem(WIDGET_ID_SHOW_WALKTHROUGH, widgetId);
+ }
+ }
+
dispatch(
bindDataToWidget({
widgetId: widgetId,
}),
);
+
+ if (isWalkthroughOpened) {
+ closeWalkthrough();
+ }
};
const isTableWidgetPresentOnCanvas = () => {
@@ -443,8 +468,8 @@ function SuggestedWidgets(props: SuggestedWidgetProps) {
const isWidgetsPresentOnCanvas = Object.keys(canvasWidgets).length > 0;
const checkAndShowWalkthrough = async () => {
- const isFeatureWalkthroughShown = await getFeatureFlagShownStatus(
- FEATURE_FLAG.ab_ds_binding_enabled,
+ const isFeatureWalkthroughShown = await getFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_binding_enabled,
);
const isNewUser = user && (await isUserSignedUpFlagSet(user.email));
@@ -455,14 +480,8 @@ function SuggestedWidgets(props: SuggestedWidgetProps) {
pushFeature({
targetId: BINDING_SECTION_ID,
onDismiss: async () => {
- AnalyticsUtil.logEvent("WALKTHROUGH_DISMISSED", {
- [AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
- FEATURE_FLAG.ab_ds_binding_enabled,
- [AB_TESTING_EVENT_KEYS.abTestingFlagValue]:
- isEnabledForQueryBinding,
- });
- await setFeatureFlagShownStatus(
- FEATURE_FLAG.ab_ds_binding_enabled,
+ await setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_binding_enabled,
true,
);
},
@@ -479,7 +498,7 @@ function SuggestedWidgets(props: SuggestedWidgetProps) {
},
eventParams: {
[AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
- FEATURE_FLAG.ab_ds_binding_enabled,
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_binding_enabled,
[AB_TESTING_EVENT_KEYS.abTestingFlagValue]: isEnabledForQueryBinding,
},
delay: 5000,
diff --git a/app/client/src/components/editorComponents/ActionRightPane/index.tsx b/app/client/src/components/editorComponents/ActionRightPane/index.tsx
index 7132626b00..acadfb4ea6 100644
--- a/app/client/src/components/editorComponents/ActionRightPane/index.tsx
+++ b/app/client/src/components/editorComponents/ActionRightPane/index.tsx
@@ -55,18 +55,18 @@ import { DatasourceComponentTypes } from "api/PluginApi";
import { fetchDatasourceStructure } from "actions/datasourceActions";
import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
import {
- getFeatureFlagShownStatus,
+ getFeatureWalkthroughShown,
isUserSignedUpFlagSet,
- setFeatureFlagShownStatus,
+ setFeatureWalkthroughShown,
} from "utils/storage";
+import { SCHEMA_SECTION_ID } from "entities/Action";
import { getCurrentUser } from "selectors/usersSelectors";
import { Tooltip } from "design-system";
import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
+import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
const SCHEMA_GUIDE_GIF = `${ASSETS_CDN_URL}/schema.gif`;
-const SCHEMA_SECTION_ID = "t--api-right-pane-schema";
-
const SideBar = styled.div`
height: 100%;
width: 100%;
@@ -378,8 +378,8 @@ function ActionSidebar({
}, []);
const checkAndShowWalkthrough = async () => {
- const isFeatureWalkthroughShown = await getFeatureFlagShownStatus(
- FEATURE_FLAG.ab_ds_schema_enabled,
+ const isFeatureWalkthroughShown = await getFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_schema_enabled,
);
const isNewUser = user && (await isUserSignedUpFlagSet(user.email));
@@ -390,13 +390,8 @@ function ActionSidebar({
pushFeature({
targetId: SCHEMA_SECTION_ID,
onDismiss: async () => {
- AnalyticsUtil.logEvent("WALKTHROUGH_DISMISSED", {
- [AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
- FEATURE_FLAG.ab_ds_schema_enabled,
- [AB_TESTING_EVENT_KEYS.abTestingFlagValue]: isEnabledForDSSchema,
- });
- await setFeatureFlagShownStatus(
- FEATURE_FLAG.ab_ds_schema_enabled,
+ await setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_schema_enabled,
true,
);
},
@@ -416,7 +411,7 @@ function ActionSidebar({
},
eventParams: {
[AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
- FEATURE_FLAG.ab_ds_schema_enabled,
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_schema_enabled,
[AB_TESTING_EVENT_KEYS.abTestingFlagValue]: isEnabledForDSSchema,
},
delay: 5000,
diff --git a/app/client/src/components/editorComponents/PropertyPaneSidebar.tsx b/app/client/src/components/editorComponents/PropertyPaneSidebar.tsx
index e4d086810b..2f5bc16434 100644
--- a/app/client/src/components/editorComponents/PropertyPaneSidebar.tsx
+++ b/app/client/src/components/editorComponents/PropertyPaneSidebar.tsx
@@ -1,7 +1,7 @@
import classNames from "classnames";
import * as Sentry from "@sentry/react";
import { useSelector } from "react-redux";
-import React, { memo, useEffect, useMemo, useRef } from "react";
+import React, { memo, useContext, useEffect, useMemo, useRef } from "react";
import PerformanceTracker, {
PerformanceTransactionName,
@@ -21,6 +21,9 @@ import { getIsAppSettingsPaneOpen } from "selectors/appSettingsPaneSelectors";
import AppSettingsPane from "pages/Editor/AppSettingsPane";
import { APP_SETTINGS_PANE_WIDTH } from "constants/AppConstants";
import styled from "styled-components";
+import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
+
+export const PROPERTY_PANE_ID = "t--property-pane-sidebar";
const StyledResizer = styled.div<{ resizing: boolean }>`
${(props) =>
@@ -54,6 +57,8 @@ export const PropertyPaneSidebar = memo((props: Props) => {
const selectedWidgetIds = useSelector(getSelectedWidgets);
const isDraggingOrResizing = useSelector(getIsDraggingOrResizing);
const isAppSettingsPaneOpen = useSelector(getIsAppSettingsPaneOpen);
+ const { isOpened: isWalkthroughOpened, popFeature } =
+ useContext(WalkthroughContext) || {};
//while dragging or resizing and
//the current selected WidgetId is not equal to previous widget id,
@@ -109,6 +114,21 @@ export const PropertyPaneSidebar = memo((props: Props) => {
keepThemeWhileDragging,
]);
+ const closeWalkthrough = () => {
+ if (popFeature) {
+ popFeature("PROPERTY_PANE");
+ sidebarRef.current?.removeEventListener("click", closeWalkthrough);
+ }
+ };
+
+ useEffect(() => {
+ if (isWalkthroughOpened)
+ sidebarRef.current?.addEventListener("click", closeWalkthrough);
+ return () => {
+ sidebarRef.current?.removeEventListener("click", closeWalkthrough);
+ };
+ }, [isWalkthroughOpened]);
+
return (
{/* PROPERTY PANE */}
@@ -119,6 +139,7 @@ export const PropertyPaneSidebar = memo((props: Props) => {
"relative ": !isPreviewMode,
"fixed translate-x-full right-0": isPreviewMode,
})}
+ id={PROPERTY_PANE_ID}
ref={sidebarRef}
>
{/* RESIZER */}
diff --git a/app/client/src/components/featureWalkthrough/index.tsx b/app/client/src/components/featureWalkthrough/index.tsx
index b3f81f92ca..71f26aef4b 100644
--- a/app/client/src/components/featureWalkthrough/index.tsx
+++ b/app/client/src/components/featureWalkthrough/index.tsx
@@ -6,6 +6,7 @@ import { createPortal } from "react-dom";
import { hideIndicator } from "pages/Editor/GuidedTour/utils";
import { retryPromise } from "utils/AppsmithUtils";
import { useLocation } from "react-router-dom";
+import AnalyticsUtil from "utils/AnalyticsUtil";
const WalkthroughRenderer = lazy(() => {
return retryPromise(
@@ -36,8 +37,16 @@ export default function Walkthrough({ children }: any) {
updateActiveWalkthrough();
};
- const popFeature = () => {
+ const popFeature = (triggeredFrom?: string) => {
hideIndicator();
+ const eventParams = activeWalkthrough?.eventParams || {};
+ if (triggeredFrom) {
+ eventParams.from = triggeredFrom;
+ }
+ AnalyticsUtil.logEvent("WALKTHROUGH_DISMISSED", eventParams);
+ if (activeWalkthrough && activeWalkthrough.onDismiss) {
+ activeWalkthrough.onDismiss();
+ }
setFeature((e) => {
e.shift();
return [...e];
@@ -46,7 +55,7 @@ export default function Walkthrough({ children }: any) {
const updateActiveWalkthrough = () => {
if (feature.length > 0) {
- const highlightArea = document.querySelector(`#${feature[0].targetId}`);
+ const highlightArea = document.getElementById(feature[0].targetId);
setActiveWalkthrough(null);
if (highlightArea) {
setTimeout(() => {
diff --git a/app/client/src/components/featureWalkthrough/walkthroughContext.tsx b/app/client/src/components/featureWalkthrough/walkthroughContext.tsx
index 28ec14ac76..2035132006 100644
--- a/app/client/src/components/featureWalkthrough/walkthroughContext.tsx
+++ b/app/client/src/components/featureWalkthrough/walkthroughContext.tsx
@@ -42,11 +42,13 @@ export type FeatureParams = {
eventParams?: Record
;
// Walkthrough delay in ms
delay?: number;
+ // Multiple Highlights -> multiple ids for highlighter, if not present considers targetId as the only highlighting div.
+ multipleHighlights?: string[];
};
type WalkthroughContextType = {
pushFeature: (feature: FeatureParams) => void;
- popFeature: () => void;
+ popFeature: (triggeredFrom?: string) => void;
feature: FeatureParams[];
isOpened: boolean;
};
diff --git a/app/client/src/components/featureWalkthrough/walkthroughRenderer.tsx b/app/client/src/components/featureWalkthrough/walkthroughRenderer.tsx
index 5cdd21b90e..bc5360c049 100644
--- a/app/client/src/components/featureWalkthrough/walkthroughRenderer.tsx
+++ b/app/client/src/components/featureWalkthrough/walkthroughRenderer.tsx
@@ -82,7 +82,7 @@ type RefRectParams = {
};
/*
- * Clip Path Polygon :
+ * Clip Path Polygon for single target with bounding rect :
* 1) 0 0 ----> (body start) (body start)
* 2) 0 ${boundingRect.bh} ----> (body start) (body end)
* 3) ${boundingRect.tx} ${boundingRect.bh} ----> (target start) (body end)
@@ -108,6 +108,9 @@ type RefRectParams = {
* ↓ ↑↓ ↑
* ↓ ↑↓ ↑
* 2 → → → → 3,8 → → → → → → → → → → → 9
+ *
+ * Repeat steps 3 to 8 for each element if there are multiple highlighting elements.
+ *
*/
/**
@@ -115,43 +118,62 @@ type RefRectParams = {
* @param targetId Id for the target container to show highlighting around it
*/
+type BoundingRectTargets = Record;
+
const WalkthroughRenderer = ({
details,
offset,
- onDismiss,
targetId,
eventParams = {},
+ multipleHighlights,
}: FeatureParams) => {
- const [boundingRect, setBoundingRect] = useState(null);
+ const [boundingRects, setBoundingRects] =
+ useState(null);
const { popFeature } = useContext(WalkthroughContext) || {};
+
+ const multipleHighlightsIds = multipleHighlights?.length
+ ? multipleHighlights
+ : [targetId];
+ if (multipleHighlightsIds.indexOf(targetId) === -1)
+ multipleHighlightsIds.push(targetId);
const updateBoundingRect = () => {
- const highlightArea = document.querySelector(`#${targetId}`);
- if (highlightArea) {
- const boundingRect = highlightArea.getBoundingClientRect();
- const bodyRect = document.body.getBoundingClientRect();
- const offsetHighlightPad =
- typeof offset?.highlightPad === "number"
- ? offset?.highlightPad
- : PADDING_HIGHLIGHT;
- setBoundingRect({
- bw: bodyRect.width,
- bh: bodyRect.height,
- tw: boundingRect.width + 2 * offsetHighlightPad,
- th: boundingRect.height + 2 * offsetHighlightPad,
- tx: boundingRect.x - offsetHighlightPad,
- ty: boundingRect.y - offsetHighlightPad,
- });
- showIndicator(`#${targetId}`, offset?.position, {
- top: offset?.indicatorTop || 0,
- left: offset?.indicatorLeft || 0,
- zIndex: Z_INDEX + 1,
+ const mainTarget = document.getElementById(targetId);
+ if (mainTarget) {
+ const data: BoundingRectTargets = {};
+ multipleHighlightsIds.forEach((id) => {
+ const highlightArea = document.getElementById(id);
+ if (highlightArea) {
+ const boundingRect = highlightArea.getBoundingClientRect();
+ const bodyRect = document.body.getBoundingClientRect();
+ const offsetHighlightPad =
+ typeof offset?.highlightPad === "number"
+ ? offset?.highlightPad
+ : PADDING_HIGHLIGHT;
+ data[id] = {
+ bw: bodyRect.width,
+ bh: bodyRect.height,
+ tw: boundingRect.width + 2 * offsetHighlightPad,
+ th: boundingRect.height + 2 * offsetHighlightPad,
+ tx: boundingRect.x - offsetHighlightPad,
+ ty: boundingRect.y - offsetHighlightPad,
+ };
+ }
});
+
+ if (Object.keys(data).length > 0) {
+ setBoundingRects(data);
+ showIndicator(`#${targetId}`, offset?.position, {
+ top: offset?.indicatorTop || 0,
+ left: offset?.indicatorLeft || 0,
+ zIndex: Z_INDEX + 1,
+ });
+ }
}
};
useEffect(() => {
updateBoundingRect();
- const highlightArea = document.querySelector(`#${targetId}`);
+ const highlightArea = document.getElementById(targetId);
window.addEventListener("resize", updateBoundingRect);
const resizeObserver = new ResizeObserver(updateBoundingRect);
if (highlightArea) {
@@ -165,37 +187,42 @@ const WalkthroughRenderer = ({
}, [targetId]);
const onDismissWalkthrough = () => {
- onDismiss && onDismiss();
- popFeature && popFeature();
+ popFeature && popFeature("WALKTHROUGH_CROSS_ICON");
};
- if (!boundingRect) return null;
+ if (!boundingRects || Object.keys(boundingRects).length === 0) return null;
+ const targetBounds = boundingRects[targetId];
+
+ if (!targetBounds) return null;
return (
{
+ const boundingRect = boundingRects[id];
+ acc = `${acc} ${boundingRect.tx} ${boundingRect.bh},
+ ${boundingRect.tx} ${boundingRect.ty},
+ ${boundingRect.tx + boundingRect.tw} ${boundingRect.ty},
+ ${boundingRect.tx + boundingRect.tw} ${
+ boundingRect.ty + boundingRect.th
+ },
+ ${boundingRect.tx} ${boundingRect.ty + boundingRect.th},
+ ${boundingRect.tx} ${boundingRect.bh},`;
+ return acc;
+ }, "")}
+ ${targetBounds.bw} ${targetBounds.bh},
+ ${targetBounds.bw} 0
+ `}
/>
@@ -203,9 +230,9 @@ const WalkthroughRenderer = ({
style={{
clipPath: 'url("#' + CLIPID + '")',
fill: "currentcolor",
- height: boundingRect.bh,
+ height: targetBounds.bh,
pointerEvents: "auto",
- width: boundingRect.bw,
+ width: targetBounds.bw,
}}
/>
diff --git a/app/client/src/constants/WalkthroughConstants.ts b/app/client/src/constants/WalkthroughConstants.ts
new file mode 100644
index 0000000000..1b0fbec3ec
--- /dev/null
+++ b/app/client/src/constants/WalkthroughConstants.ts
@@ -0,0 +1,7 @@
+import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
+
+export const FEATURE_WALKTHROUGH_KEYS = {
+ [FEATURE_FLAG.ab_ds_binding_enabled]: FEATURE_FLAG.ab_ds_binding_enabled,
+ [FEATURE_FLAG.ab_ds_schema_enabled]: FEATURE_FLAG.ab_ds_schema_enabled,
+ binding_widget: "binding_widget",
+};
diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx
index c44f3cb0b0..cc7326294a 100644
--- a/app/client/src/constants/WidgetConstants.tsx
+++ b/app/client/src/constants/WidgetConstants.tsx
@@ -252,3 +252,6 @@ export const SUGGESTED_WIDGETS_ORDER: Record = {
SELECT_WIDGET: 5,
LIST_WIDGET_V2: 6,
};
+
+// Constant key to show walkthrough for a widget -> stores widget id
+export const WIDGET_ID_SHOW_WALKTHROUGH = "WIDGET_ID_SHOW_WALKTHROUGH";
diff --git a/app/client/src/entities/Action/index.ts b/app/client/src/entities/Action/index.ts
index 18ea8f72bb..f27eed1f2f 100644
--- a/app/client/src/entities/Action/index.ts
+++ b/app/client/src/entities/Action/index.ts
@@ -239,3 +239,5 @@ export function getGraphQLPlugin(plugins: Plugin[]): Plugin | undefined {
export function isGraphqlPlugin(plugin: Plugin | undefined) {
return plugin?.packageName === PluginPackageName.GRAPHQL;
}
+
+export const SCHEMA_SECTION_ID = "t--api-right-pane-schema";
diff --git a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx
index 13a5123b12..e1c2c6a446 100644
--- a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx
+++ b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useContext } from "react";
+import React, { useState } from "react";
import Entity, { EntityClassNames } from "../Entity";
import { datasourceTableIcon } from "../ExplorerIcons";
import QueryTemplates from "./QueryTemplates";
@@ -17,9 +17,6 @@ import styled from "styled-components";
import { DatasourceStructureContext } from "./DatasourceStructureContainer";
import AnalyticsUtil from "utils/AnalyticsUtil";
import type { Plugin } from "api/PluginApi";
-import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
-import { setFeatureFlagShownStatus } from "utils/storage";
-import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
type DatasourceStructureProps = {
dbStructure: DatasourceTable;
@@ -42,9 +39,6 @@ export function DatasourceStructure(props: DatasourceStructureProps) {
const [active, setActive] = useState(false);
useCloseMenuOnScroll(SIDEBAR_ID, active, () => setActive(false));
- const { isOpened: isWalkthroughOpened, popFeature } =
- useContext(WalkthroughContext) || {};
-
const datasource = useSelector((state: AppState) =>
getDatasource(state, props.datasourceId),
);
@@ -72,15 +66,6 @@ export function DatasourceStructure(props: DatasourceStructureProps) {
});
canCreateDatasourceActions && setActive(!active);
-
- dbStructure.templates.length === 0 &&
- isWalkthroughOpened &&
- closeWalkthrough();
- };
-
- const closeWalkthrough = () => {
- popFeature && popFeature();
- setFeatureFlagShownStatus(FEATURE_FLAG.ab_ds_schema_enabled, true);
};
const lightningMenu =
diff --git a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructureContainer.tsx b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructureContainer.tsx
index a31d194317..411881bd29 100644
--- a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructureContainer.tsx
+++ b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructureContainer.tsx
@@ -9,6 +9,7 @@ import type {
DatasourceTable,
} from "entities/Datasource";
import type { ReactElement } from "react";
+import { useContext } from "react";
import React, { memo, useEffect, useMemo, useState } from "react";
import EntityPlaceholder from "../Entity/Placeholder";
import DatasourceStructure from "./DatasourceStructure";
@@ -21,6 +22,10 @@ import DatasourceStructureLoadingContainer from "./DatasourceStructureLoadingCon
import DatasourceStructureNotFound from "./DatasourceStructureNotFound";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { PluginName } from "entities/Action";
+import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
+import { setFeatureWalkthroughShown } from "utils/storage";
+import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
+import { SCHEMA_SECTION_ID } from "entities/Action";
type Props = {
datasourceId: string;
@@ -71,6 +76,35 @@ const Container = (props: Props) => {
>(props.datasourceStructure);
const [hasSearchedOccured, setHasSearchedOccured] = useState(false);
+ const { isOpened: isWalkthroughOpened, popFeature } =
+ useContext(WalkthroughContext) || {};
+
+ const attachCloseWalkthrough =
+ props.context !== DatasourceStructureContext.EXPLORER &&
+ isWalkthroughOpened &&
+ !isLoading &&
+ !props.datasourceStructure?.tables?.length;
+
+ const closeWalkthrough = () => {
+ popFeature && popFeature("DATASOURCE_SCHEMA_CONTAINER");
+ setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_schema_enabled,
+ true,
+ );
+ };
+
+ useEffect(() => {
+ const schemaContainer = document.querySelector(`#${SCHEMA_SECTION_ID}`);
+ if (schemaContainer && attachCloseWalkthrough) {
+ schemaContainer.addEventListener("click", closeWalkthrough);
+ }
+ return () => {
+ if (schemaContainer && attachCloseWalkthrough) {
+ schemaContainer.removeEventListener("click", closeWalkthrough);
+ }
+ };
+ }, [attachCloseWalkthrough]);
+
useEffect(() => {
if (datasourceStructure !== props.datasourceStructure) {
setDatasourceStructure(props.datasourceStructure);
diff --git a/app/client/src/pages/Editor/Explorer/Datasources/QueryTemplates.tsx b/app/client/src/pages/Editor/Explorer/Datasources/QueryTemplates.tsx
index 1450313194..a8d742ec40 100644
--- a/app/client/src/pages/Editor/Explorer/Datasources/QueryTemplates.tsx
+++ b/app/client/src/pages/Editor/Explorer/Datasources/QueryTemplates.tsx
@@ -21,14 +21,14 @@ import { MenuItem } from "design-system";
import type { Plugin } from "api/PluginApi";
import { DatasourceStructureContext } from "./DatasourceStructureContainer";
import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
-import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
-import { setFeatureFlagShownStatus } from "utils/storage";
+import { setFeatureWalkthroughShown } from "utils/storage";
import styled from "styled-components";
import { change, getFormValues } from "redux-form";
import { QUERY_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
import { diff } from "deep-diff";
import { UndoRedoToastContext, showUndoRedoToast } from "utils/replayHelpers";
import AnalyticsUtil from "utils/AnalyticsUtil";
+import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
type QueryTemplatesProps = {
templates: QueryTemplate[];
@@ -110,8 +110,11 @@ export function QueryTemplates(props: QueryTemplatesProps) {
);
if (isWalkthroughOpened) {
- popFeature && popFeature();
- setFeatureFlagShownStatus(FEATURE_FLAG.ab_ds_schema_enabled, true);
+ popFeature && popFeature("SCHEMA_QUERY_CREATE");
+ setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_schema_enabled,
+ true,
+ );
}
history.push(
@@ -170,8 +173,11 @@ export function QueryTemplates(props: QueryTemplatesProps) {
});
if (isWalkthroughOpened) {
- popFeature && popFeature();
- setFeatureFlagShownStatus(FEATURE_FLAG.ab_ds_schema_enabled, true);
+ popFeature && popFeature("SCHEMA_QUERY_UPDATE");
+ setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_schema_enabled,
+ true,
+ );
}
showUndoRedoToast(
diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx
index 0e2f8c01e0..d5b0c92dff 100644
--- a/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx
+++ b/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx
@@ -1,4 +1,5 @@
import type { ReactElement } from "react";
+import { useContext } from "react";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import equal from "fast-deep-equal/es6";
import { useDispatch, useSelector } from "react-redux";
@@ -12,6 +13,7 @@ import { deleteSelectedWidget, copyWidget } from "actions/widgetActions";
import ConnectDataCTA, { actionsExist } from "./ConnectDataCTA";
import PropertyPaneConnections from "./PropertyPaneConnections";
import type { WidgetType } from "constants/WidgetConstants";
+import { WIDGET_ID_SHOW_WALKTHROUGH } from "constants/WidgetConstants";
import type { InteractionAnalyticsEventDetail } from "utils/AppsmithUtils";
import { INTERACTION_ANALYTICS_EVENT } from "utils/AppsmithUtils";
import { emitInteractionAnalyticsEvent } from "utils/AppsmithUtils";
@@ -23,6 +25,18 @@ import { PropertyPaneTab } from "./PropertyPaneTab";
import { useSearchText } from "./helpers";
import { PropertyPaneSearchInput } from "./PropertyPaneSearchInput";
import { sendPropertyPaneSearchAnalytics } from "./propertyPaneSearch";
+import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
+import { AB_TESTING_EVENT_KEYS } from "@appsmith/entities/FeatureFlag";
+import localStorage from "utils/localStorage";
+import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
+import { PROPERTY_PANE_ID } from "components/editorComponents/PropertyPaneSidebar";
+import { setFeatureWalkthroughShown } from "utils/storage";
+import {
+ BINDING_WIDGET_WALKTHROUGH_DESC,
+ BINDING_WIDGET_WALKTHROUGH_TITLE,
+ createMessage,
+} from "@appsmith/constants/messages";
+import { getWidgets } from "sagas/selectors";
// TODO(abhinav): The widget should add a flag in their configuration if they donot subscribe to data
// Widgets where we do not want to show the CTA
@@ -65,6 +79,52 @@ function PropertyPaneView(
return true;
}, [widgetProperties?.type, excludeList]);
const { searchText, setSearchText } = useSearchText("");
+ const { pushFeature } = useContext(WalkthroughContext) || {};
+ const widgets = useSelector(getWidgets);
+
+ const showWalkthroughIfWidgetIdSet = async () => {
+ const widgetId: string | null = await localStorage.getItem(
+ WIDGET_ID_SHOW_WALKTHROUGH,
+ );
+
+ // Adding table condition as connecting to select, chart widgets is currently not working as expected
+ // When we fix those, we can remove this table condtion
+ const isTableWidget = !!widgetId
+ ? widgets[widgetId]?.type === "TABLE_WIDGET_V2"
+ : false;
+
+ if (widgetId && pushFeature && isTableWidget) {
+ pushFeature({
+ targetId: PROPERTY_PANE_ID,
+ onDismiss: async () => {
+ await localStorage.removeItem(WIDGET_ID_SHOW_WALKTHROUGH);
+ await setFeatureWalkthroughShown(
+ FEATURE_WALKTHROUGH_KEYS.binding_widget,
+ true,
+ );
+ },
+ details: {
+ title: createMessage(BINDING_WIDGET_WALKTHROUGH_TITLE),
+ description: createMessage(BINDING_WIDGET_WALKTHROUGH_DESC),
+ },
+ offset: {
+ position: "left",
+ left: -40,
+ top: 250,
+ highlightPad: 2,
+ indicatorLeft: -3,
+ indicatorTop: 230,
+ },
+ eventParams: {
+ [AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
+ FEATURE_WALKTHROUGH_KEYS.binding_widget,
+ [AB_TESTING_EVENT_KEYS.abTestingFlagValue]: true,
+ },
+ multipleHighlights: [widgetId, PROPERTY_PANE_ID],
+ delay: 5000,
+ });
+ }
+ };
const handleKbdEvent = (e: Event) => {
const event = e as CustomEvent;
@@ -81,6 +141,7 @@ function PropertyPaneView(
INTERACTION_ANALYTICS_EVENT,
handleKbdEvent,
);
+ showWalkthroughIfWidgetIdSet();
return () => {
containerRef.current?.removeEventListener(
INTERACTION_ANALYTICS_EVENT,
diff --git a/app/client/src/sagas/SnipingModeSagas.ts b/app/client/src/sagas/SnipingModeSagas.ts
index 1a54201c2a..364065dcd0 100644
--- a/app/client/src/sagas/SnipingModeSagas.ts
+++ b/app/client/src/sagas/SnipingModeSagas.ts
@@ -26,6 +26,7 @@ import {
FEATURE_FLAG,
} from "@appsmith/entities/FeatureFlag";
import { selectFeatureFlagCheck } from "@appsmith/selectors/featureFlagsSelectors";
+import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
import type { PropertyUpdates } from "widgets/constants";
export function* bindDataToWidgetSaga(
@@ -78,7 +79,7 @@ export function* bindDataToWidgetSaga(
propertyPath: updates?.map((update) => update.propertyPath).toString(),
propertyValue: updates?.map((update) => update.propertyPath).toString(),
[AB_TESTING_EVENT_KEYS.abTestingFlagLabel]:
- FEATURE_FLAG.ab_ds_binding_enabled,
+ FEATURE_WALKTHROUGH_KEYS.ab_ds_binding_enabled,
[AB_TESTING_EVENT_KEYS.abTestingFlagValue]: isDSBindingEnabled,
});
} else {
diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx
index 531a47fa80..9089d58bf9 100644
--- a/app/client/src/sagas/WidgetOperationSagas.tsx
+++ b/app/client/src/sagas/WidgetOperationSagas.tsx
@@ -13,6 +13,7 @@ import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
RenderModes,
+ WIDGET_ID_SHOW_WALKTHROUGH,
} from "constants/WidgetConstants";
import log from "loglevel";
import type { WidgetResize } from "actions/pageActions";
@@ -181,6 +182,7 @@ import {
FlexLayerAlignment,
LayoutDirection,
} from "utils/autoLayout/constants";
+import localStorage from "utils/localStorage";
export function* resizeSaga(resizeAction: ReduxAction) {
try {
@@ -2017,7 +2019,11 @@ function* cutWidgetSaga() {
}
function* addSuggestedWidget(action: ReduxAction>) {
+ const isSetWidgetIdForWalkthrough = !!(
+ action.payload.props.setWidgetIdForWalkthrough === "true"
+ );
const widgetConfig = action.payload;
+ delete widgetConfig.props?.setWidgetIdForWalkthrough;
if (!widgetConfig.type) return;
@@ -2079,6 +2085,10 @@ function* addSuggestedWidget(action: ReduxAction>) {
yield take(ReduxActionTypes.UPDATE_LAYOUT);
+ if (isSetWidgetIdForWalkthrough) {
+ localStorage.setItem(WIDGET_ID_SHOW_WALKTHROUGH, newWidget.newWidgetId);
+ }
+
yield put(
selectWidgetInitAction(SelectionRequestType.One, [newWidget.newWidgetId]),
);
diff --git a/app/client/src/utils/storage.ts b/app/client/src/utils/storage.ts
index 1b8220688b..f007ecd425 100644
--- a/app/client/src/utils/storage.ts
+++ b/app/client/src/utils/storage.ts
@@ -457,7 +457,7 @@ export const getAIPromptTriggered = async () => {
return 0;
}
};
-export const setFeatureFlagShownStatus = async (key: string, value: any) => {
+export const setFeatureWalkthroughShown = async (key: string, value: any) => {
try {
let flagsJSON: Record | null = await store.getItem(
STORAGE_KEYS.FEATURE_WALKTHROUGH,
@@ -477,7 +477,7 @@ export const setFeatureFlagShownStatus = async (key: string, value: any) => {
}
};
-export const getFeatureFlagShownStatus = async (key: string) => {
+export const getFeatureWalkthroughShown = async (key: string) => {
try {
const flagsJSON: Record | null = await store.getItem(
STORAGE_KEYS.FEATURE_WALKTHROUGH,
diff --git a/app/client/src/widgets/ContainerWidget/component/index.tsx b/app/client/src/widgets/ContainerWidget/component/index.tsx
index a3365d2995..b162879d0f 100644
--- a/app/client/src/widgets/ContainerWidget/component/index.tsx
+++ b/app/client/src/widgets/ContainerWidget/component/index.tsx
@@ -1,3 +1,4 @@
+import { useContext } from "react";
import type {
MouseEventHandler,
PropsWithChildren,
@@ -17,6 +18,7 @@ import { useSelector } from "react-redux";
import { getCurrentAppPositioningType } from "selectors/editorSelectors";
import { AppPositioningTypes } from "reducers/entityReducers/pageListReducer";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
+import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
const StyledContainerComponent = styled.div<
Omit
@@ -59,6 +61,8 @@ function ContainerComponentWrapper(
) {
const containerRef: RefObject = useRef(null);
const appPositioningType = useSelector(getCurrentAppPositioningType);
+ const { isOpened: isWalkthroughOpened, popFeature } =
+ useContext(WalkthroughContext) || {};
useEffect(() => {
if (!props.shouldScrollContents) {
@@ -117,6 +121,17 @@ function ContainerComponentWrapper(
[props.onClickCapture],
);
+ const closeWalkthrough = () => {
+ if (isWalkthroughOpened && popFeature) {
+ popFeature("WIDGET_CONTAINER");
+ }
+ };
+
+ const onClickHandler = (event: any) => {
+ closeWalkthrough();
+ if (props.onClick) props.onClick(event);
+ };
+
return (