feat: multiple highlight area in walkthrough, corrected walkthrough events for each feature (#25520)
## Description - Added support for multiple highlights in walkthrough - Corrected walkthrough events for each feature walkthrough - Added fix for the walkthrough dismissal in case of empty/no schema feature #### PR fixes following issue(s) Fixes #25417 Fixes #25416 #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: “sneha122” <“sneha@appsmith.com”>
This commit is contained in:
parent
2f36589171
commit
e3e75acc32
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="relative">
|
||||
{/* 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 */}
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -42,11 +42,13 @@ export type FeatureParams = {
|
|||
eventParams?: Record<string, any>;
|
||||
// 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string, RefRectParams>;
|
||||
|
||||
const WalkthroughRenderer = ({
|
||||
details,
|
||||
offset,
|
||||
onDismiss,
|
||||
targetId,
|
||||
eventParams = {},
|
||||
multipleHighlights,
|
||||
}: FeatureParams) => {
|
||||
const [boundingRect, setBoundingRect] = useState<RefRectParams | null>(null);
|
||||
const [boundingRects, setBoundingRects] =
|
||||
useState<BoundingRectTargets | null>(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 (
|
||||
<WalkthroughWrapper className="t--walkthrough-overlay">
|
||||
<SvgWrapper
|
||||
height={boundingRect.bh}
|
||||
width={boundingRect.bw}
|
||||
height={targetBounds.bh}
|
||||
width={targetBounds.bw}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<clipPath id={CLIPID}>
|
||||
<polygon
|
||||
// See the comments above the component declaration to understand the below points assignment.
|
||||
points={`
|
||||
0 0,
|
||||
0 ${boundingRect.bh},
|
||||
${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},
|
||||
${boundingRect.bw} ${boundingRect.bh},
|
||||
${boundingRect.bw} 0
|
||||
`}
|
||||
points={`0 0,
|
||||
0 ${targetBounds.bh},
|
||||
${multipleHighlightsIds.reduce((acc, id) => {
|
||||
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
|
||||
`}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
</SvgWrapper>
|
||||
|
|
|
|||
7
app/client/src/constants/WalkthroughConstants.ts
Normal file
7
app/client/src/constants/WalkthroughConstants.ts
Normal file
|
|
@ -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",
|
||||
};
|
||||
|
|
@ -252,3 +252,6 @@ export const SUGGESTED_WIDGETS_ORDER: Record<WidgetType, number> = {
|
|||
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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<InteractionAnalyticsEventDetail>;
|
||||
|
|
@ -81,6 +141,7 @@ function PropertyPaneView(
|
|||
INTERACTION_ANALYTICS_EVENT,
|
||||
handleKbdEvent,
|
||||
);
|
||||
showWalkthroughIfWidgetIdSet();
|
||||
return () => {
|
||||
containerRef.current?.removeEventListener(
|
||||
INTERACTION_ANALYTICS_EVENT,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<WidgetResize>) {
|
||||
try {
|
||||
|
|
@ -2017,7 +2019,11 @@ function* cutWidgetSaga() {
|
|||
}
|
||||
|
||||
function* addSuggestedWidget(action: ReduxAction<Partial<WidgetProps>>) {
|
||||
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<Partial<WidgetProps>>) {
|
|||
|
||||
yield take(ReduxActionTypes.UPDATE_LAYOUT);
|
||||
|
||||
if (isSetWidgetIdForWalkthrough) {
|
||||
localStorage.setItem(WIDGET_ID_SHOW_WALKTHROUGH, newWidget.newWidgetId);
|
||||
}
|
||||
|
||||
yield put(
|
||||
selectWidgetInitAction(SelectionRequestType.One, [newWidget.newWidgetId]),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<string, any> | 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<string, any> | null = await store.getItem(
|
||||
STORAGE_KEYS.FEATURE_WALKTHROUGH,
|
||||
|
|
|
|||
|
|
@ -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<ContainerWrapperProps, "widgetId">
|
||||
|
|
@ -59,6 +61,8 @@ function ContainerComponentWrapper(
|
|||
) {
|
||||
const containerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(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 (
|
||||
<StyledContainerComponent
|
||||
// Before you remove: generateClassName is used for bounding the resizables within this canvas
|
||||
|
|
@ -133,7 +148,7 @@ function ContainerComponentWrapper(
|
|||
}`}
|
||||
data-widgetId={props.widgetId}
|
||||
dropDisabled={props.dropDisabled}
|
||||
onClick={props.onClick}
|
||||
onClick={onClickHandler}
|
||||
onClickCapture={props.onClickCapture}
|
||||
onMouseOver={onMouseOver}
|
||||
ref={containerRef}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user