diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_dataToTableWithSnipingMode_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_dataToTableWithSnipingMode_spec.js new file mode 100644 index 0000000000..546ee2b23a --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_dataToTableWithSnipingMode_spec.js @@ -0,0 +1,27 @@ +const dsl = require("../../../../fixtures/tableWidgetDsl.json"); + +describe("Test Create Api and Bind to Table widget", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Test_Add users api, execute it and go to sniping mode.", function() { + cy.createAndFillApi(this.data.userApi, "/users"); + cy.RunAPI(); + cy.get(".t--select-in-canvas").click(); + cy.get(".t--sniping-mode-banner").should("be.visible"); + }); + + it("Click on table name controller to bind the data and exit sniping mode", function() { + cy.get(".t--draggable-tablewidget").trigger("mouseover"); + cy.get(".t--settings-sniping-control").click(); + cy.get(".t--property-control-tabledata .CodeMirror").contains( + "{{Api1.data}}", + ); + cy.get(".t--sniping-mode-banner").should("not.exist"); + }); + + afterEach(() => { + // put your clean up code if any + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js index f565c11a99..f79f00eb11 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js @@ -11,6 +11,6 @@ describe("Inspect Entity", function() { cy.get(".t--debugger").click(); cy.contains(".react-tabs__tab", "Inspect Entity").click(); cy.contains(".t--dependencies-item", "Button1").click(); - cy.contains(".t--references-item", "Input1"); + cy.contains(".t--dependencies-item", "Input1"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js index 2559c6ad4b..859aaf4485 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js @@ -42,7 +42,7 @@ describe("Addwidget from Query and bind with other widgets", function() { "response.body.responseMeta.status", 200, ); - cy.get(".t--add-widget").click(); + cy.get(queryEditor.suggestedTableWidget).click(); cy.SearchEntityandOpen("Table1"); cy.isSelectRow(1); cy.readTabledataPublish("1", "0").then((tabData) => { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js index 281400a1ff..8aa49b2fdb 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js @@ -31,7 +31,7 @@ describe("Add widget", function() { "response.body.responseMeta.status", 200, ); - cy.get(".t--add-widget").click(); + cy.get(queryEditor.suggestedTableWidget).click(); cy.SearchEntityandOpen("Table1"); cy.isSelectRow(1); cy.readTabledataPublish("1", "0").then((tabData) => { diff --git a/app/client/cypress/locators/QueryEditor.json b/app/client/cypress/locators/QueryEditor.json index 1d736eda63..6eeb4e3cfd 100644 --- a/app/client/cypress/locators/QueryEditor.json +++ b/app/client/cypress/locators/QueryEditor.json @@ -14,5 +14,6 @@ "queryNameField": ".t--action-name-edit-field input", "settings": "li:contains('Settings')", "query": "li:contains('Query')", - "switch": ".t--form-control-SWITCH input" + "switch": ".t--form-control-SWITCH input", + "suggestedTableWidget": ".t--suggested-widget-TABLE_WIDGET" } \ No newline at end of file diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index a52a3d3788..6742e3e354 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -269,6 +269,17 @@ export const setActionsToExecuteOnPageLoad = ( }; }; +export const bindDataOnCanvas = (payload: { + queryId: string; + applicationId: string; + pageId: string; +}) => { + return { + type: ReduxActionTypes.BIND_DATA_ON_CANVAS, + payload, + }; +}; + export default { createAction: createActionRequest, fetchActions, @@ -277,4 +288,5 @@ export default { deleteActionSuccess, updateAction, updateActionSuccess, + bindDataOnCanvas, }; diff --git a/app/client/src/actions/propertyPaneActions.ts b/app/client/src/actions/propertyPaneActions.ts index a90d3a1453..57551ab0cf 100644 --- a/app/client/src/actions/propertyPaneActions.ts +++ b/app/client/src/actions/propertyPaneActions.ts @@ -14,3 +14,19 @@ export const hidePropertyPane = () => { type: ReduxActionTypes.HIDE_PROPERTY_PANE, }; }; + +export const bindDataToWidget = (payload: { widgetId: string }) => { + return { + type: ReduxActionTypes.BIND_DATA_TO_WIDGET, + payload, + }; +}; + +export const setSnipingMode = (payload: boolean) => ({ + type: ReduxActionTypes.SET_SNIPING_MODE, + payload, +}); + +export const resetSnipingMode = () => ({ + type: ReduxActionTypes.RESET_SNIPING_MODE, +}); diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx index d67748f1c2..de610d7946 100644 --- a/app/client/src/actions/widgetActions.tsx +++ b/app/client/src/actions/widgetActions.tsx @@ -12,6 +12,7 @@ import { BatchAction, batchAction } from "actions/batchActions"; import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; +import { WidgetProps } from "widgets/BaseWidget"; export const executeAction = ( payload: ExecuteActionPayload, @@ -144,9 +145,9 @@ export const cutWidget = () => { }; }; -export const addTableWidgetFromQuery = (queryName: string) => { +export const addSuggestedWidget = (payload: Partial) => { return { - type: ReduxActionTypes.ADD_TABLE_WIDGET_FROM_QUERY, - payload: queryName, + type: ReduxActionTypes.ADD_SUGGESTED_WIDGET, + payload, }; }; diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 9c6a908bd1..cf0ed0c2e1 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -4,6 +4,7 @@ import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants"; import axios, { AxiosPromise, CancelTokenSource } from "axios"; import { Action, ActionViewMode } from "entities/Action"; import { APIRequest } from "constants/AppsmithActionConstants/ActionConstants"; +import { WidgetType } from "constants/WidgetConstants"; export interface CreateActionRequest extends APIRequest { datasourceId: string; @@ -75,6 +76,11 @@ export interface ActionExecutionResponse { }; } +export interface SuggestedWidget { + type: WidgetType; + bindingQuery: string; +} + export interface ActionResponse { body: unknown; headers: Record; @@ -83,6 +89,7 @@ export interface ActionResponse { duration: string; size: string; isExecutionSuccess?: boolean; + suggestedWidgets?: SuggestedWidget[]; messages?: Array; } diff --git a/app/client/src/assets/images/long-arrow-bottom.svg b/app/client/src/assets/images/long-arrow-bottom.svg new file mode 100644 index 0000000000..4da5972f74 --- /dev/null +++ b/app/client/src/assets/images/long-arrow-bottom.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/images/long-arrow-right.svg b/app/client/src/assets/images/long-arrow-right.svg new file mode 100644 index 0000000000..961c7eaac9 --- /dev/null +++ b/app/client/src/assets/images/long-arrow-right.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx index 5e5bed6124..a6939fb721 100644 --- a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx +++ b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx @@ -4,8 +4,9 @@ import { WIDGET_PADDING } from "constants/WidgetConstants"; import { generateClassName } from "utils/generators"; import styled from "styled-components"; import { useClickOpenPropPane } from "utils/hooks/useClickOpenPropPane"; -import { stopEventPropagation } from "utils/AppsmithUtils"; import { Layers } from "constants/Layers"; +import { useSelector } from "react-redux"; +import { snipingModeSelector } from "../../../selectors/editorSelectors"; const PositionedWidget = styled.div` &:hover { @@ -27,6 +28,7 @@ export function PositionedContainer(props: PositionedContainerProps) { const y = props.style.yPosition + (props.style.yPositionUnit || "px"); const padding = WIDGET_PADDING; const openPropertyPane = useClickOpenPropPane(); + const isSnipingMode = useSelector(snipingModeSelector); // memoized classname const containerClassName = useMemo(() => { return ( @@ -54,10 +56,17 @@ export function PositionedContainer(props: PositionedContainerProps) { }; }, [props.style]); - const openPropPane = useCallback((e) => openPropertyPane(e, props.widgetId), [ - props.widgetId, - openPropertyPane, - ]); + const openPropPane = useCallback( + (e) => { + openPropertyPane(e, props.widgetId); + }, + [props.widgetId, openPropertyPane], + ); + + // TODO: Experimental fix for sniping mode. This should be handled with a single event + const stopEventPropagation = (e: any) => { + !isSnipingMode && e.stopPropagation(); + }; return ( props.theme.spaces[2] - 1}px; + } + padding-bottom: ${(props) => props.theme.spaces[2]}px; +`; + +const ConnectionWrapper = styled.div` + margin: ${(props) => props.theme.spaces[1]}px + ${(props) => props.theme.spaces[0] + 2}px; +`; + +const NoConnections = styled.div` + width: 100%; + background-color: ${(props) => + props.theme.colors.actionSidePane.noConnections}; + padding: ${(props) => props.theme.spaces[4] + 1}px + ${(props) => props.theme.spaces[3]}px; + + .${Classes.TEXT} { + color: ${(props) => props.theme.colors.actionSidePane.noConnectionsText}; + } +`; + +const ConnectionFlow = styled.div` + display: flex; + align-items: center; + flex-direction: column; + + img { + padding-top: ${(props) => props.theme.spaces[1]}px; + padding-bottom: ${(props) => props.theme.spaces[2] + 1}px; + } +`; + +const ConnectionsContainer = styled.span` + width: 100%; + background-color: ${(props) => + props.theme.colors.actionSidePane.noConnections}; + display: flex; + flex-wrap: wrap; + padding: ${(props) => props.theme.spaces[2] + 1}px; + .connection { + border: 1px solid + ${(props) => props.theme.colors.actionSidePane.connectionBorder}; + padding: ${(props) => props.theme.spaces[0] + 2}px + ${(props) => props.theme.spaces[1]}px; + ${(props) => getTypographyByKey(props, "p3")} + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + + :hover { + border: 1px solid + ${(props) => props.theme.colors.actionSidePane.connectionHover}; + color: ${(props) => props.theme.colors.actionSidePane.connectionHover}; + } + } +`; + +const useGetEntityType = () => { + const dataTree = useSelector(getDataTree); + + const getEntityType = useCallback((name) => { + if (isWidget(dataTree[name])) { + return "widget"; + } else if (isAction(dataTree[name])) { + return "integration"; + } + }, []); + + return getEntityType; +}; + +function Dependencies(props: any) { + const { navigateToEntity } = useEntityLink(); + const getEntityType = useGetEntityType(); + + const onClick = (entityName: string) => { + navigateToEntity(entityName); + AnalyticsUtil.logEvent("ASSOCIATED_ENTITY_CLICK", { + screen: "INTEGRATION", + }); + }; + + return props.dependencies.length ? ( + + {props.dependencies.map((entityName: string) => { + const entityType = getEntityType(entityName); + + return ( + + + onClick(entityName)}> + {entityName} + + + + ); + })} + + ) : ( + + {props.placeholder} + + ); +} + +type ConnectionsProps = { + actionName: string; + entityDependencies: { + inverseDependencies: string[]; + directDependencies: string[]; + } | null; +}; + +function Connections(props: ConnectionsProps) { + return ( + + + {createMessage(SEE_CONNECTED_ENTITIES)} + + + + + {createMessage(INCOMING_ENTITIES)} + + + {/* Direct Dependencies */} + + + + {props.actionName} + + + + + {createMessage(OUTGOING_ENTITIES)} + + + + {/* Inverse dependencies */} + + + ); +} + +export default Connections; diff --git a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx new file mode 100644 index 0000000000..87d156c544 --- /dev/null +++ b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx @@ -0,0 +1,211 @@ +import { getTypographyByKey } from "constants/DefaultTheme"; +import { WidgetTypes } from "constants/WidgetConstants"; +import React from "react"; +import { useDispatch } from "react-redux"; +import styled from "styled-components"; +import { generateReactKey } from "utils/generators"; +import { Collapsible } from "."; +import Tooltip from "components/ads/Tooltip"; +import { addSuggestedWidget } from "actions/widgetActions"; +import AnalyticsUtil from "utils/AnalyticsUtil"; +import { + ADD_NEW_WIDGET, + createMessage, + SUGGESTED_WIDGETS, + SUGGESTED_WIDGET_DESCRIPTION, + SUGGESTED_WIDGET_TOOLTIP, +} from "constants/messages"; +import { SuggestedWidget } from "api/ActionAPI"; + +const WidgetList = styled.div` + ${(props) => getTypographyByKey(props, "p1")} + margin-left: ${(props) => props.theme.spaces[2] + 1}px; + + img { + max-width: 100%; + } + + .image-wrapper { + position: relative; + margin-top: ${(props) => props.theme.spaces[1]}px; + } + + .widget:hover { + cursor: pointer; + } + + .widget:not(:first-child) { + margin-top: 24px; + } +`; + +const WidgetOverlay = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: calc(100% - ${(props) => props.theme.spaces[1]}px); + + &:hover { + display: block; + background: rgba(0, 0, 0, 0.6); + } +`; + +type WidgetBindingInfo = { + label: string; + propertyName: string; + widgetName: string; + image?: string; +}; + +export const WIDGET_DATA_FIELD_MAP: Record = { + [WidgetTypes.LIST_WIDGET]: { + label: "items", + propertyName: "listData", + widgetName: "List", + image: + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com/widgetSuggestion/list.svg", + }, + [WidgetTypes.TABLE_WIDGET]: { + label: "tabledata", + propertyName: "tableData", + widgetName: "Table", + image: + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com/widgetSuggestion/table.svg", + }, + [WidgetTypes.CHART_WIDGET]: { + label: "chart-series-data-control", + propertyName: "chartData", + widgetName: "Chart", + image: + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com/widgetSuggestion/chart.svg", + }, + [WidgetTypes.DROP_DOWN_WIDGET]: { + label: "options", + propertyName: "options", + widgetName: "Select", + image: + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com/widgetSuggestion/dropdown.svg", + }, + [WidgetTypes.TEXT_WIDGET]: { + label: "text", + propertyName: "text", + widgetName: "Text", + image: + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com/widgetSuggestion/text.svg", + }, + [WidgetTypes.INPUT_WIDGET]: { + label: "text", + propertyName: "defaultText", + widgetName: "Input", + image: + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com/widgetSuggestion/input.svg", + }, +}; + +function getWidgetProps( + suggestedWidget: SuggestedWidget, + widgetInfo: WidgetBindingInfo, + actionName: string, +) { + const fieldName = widgetInfo.propertyName; + switch (suggestedWidget.type) { + case WidgetTypes.TABLE_WIDGET: + return { + type: WidgetTypes.TABLE_WIDGET, + props: { + [fieldName]: `{{${actionName}.${suggestedWidget.bindingQuery}}}`, + dynamicBindingPathList: [{ key: "tableData" }], + }, + parentRowSpace: 10, + }; + case WidgetTypes.CHART_WIDGET: + const reactKey = generateReactKey(); + + return { + type: suggestedWidget.type, + props: { + [fieldName]: { + [reactKey]: { + seriesName: "Sales", + data: `{{${actionName}.${suggestedWidget.bindingQuery}}}`, + }, + }, + dynamicBindingPathList: [{ key: `chartData.${reactKey}.data` }], + }, + }; + default: + return { + type: suggestedWidget.type, + props: { + [fieldName]: `{{${actionName}.${suggestedWidget.bindingQuery}}}`, + dynamicBindingPathList: [{ key: widgetInfo.propertyName }], + }, + }; + } +} + +type SuggestedWidgetProps = { + actionName: string; + suggestedWidgets: SuggestedWidget[]; + hasWidgets: boolean; +}; + +function SuggestedWidgets(props: SuggestedWidgetProps) { + const dispatch = useDispatch(); + + const addWidget = ( + suggestedWidget: SuggestedWidget, + widgetInfo: WidgetBindingInfo, + ) => { + const payload = getWidgetProps( + suggestedWidget, + widgetInfo, + props.actionName, + ); + + AnalyticsUtil.logEvent("SUGGESTED_WIDGET_CLICK", { + widget: suggestedWidget.type, + }); + + dispatch(addSuggestedWidget(payload)); + }; + + const label = props.hasWidgets + ? createMessage(ADD_NEW_WIDGET) + : createMessage(SUGGESTED_WIDGETS); + + return ( + +
+ {createMessage(SUGGESTED_WIDGET_DESCRIPTION)}{" "} +
+ + {props.suggestedWidgets.map((suggestedWidget) => { + const widgetInfo: WidgetBindingInfo | undefined = + WIDGET_DATA_FIELD_MAP[suggestedWidget.type]; + + if (!widgetInfo) return null; + + return ( +
addWidget(suggestedWidget, widgetInfo)} + > + +
+ {widgetInfo.image && } + +
+
+
+ ); + })} +
+
+ ); +} + +export default SuggestedWidgets; diff --git a/app/client/src/components/editorComponents/ActionRightPane/index.tsx b/app/client/src/components/editorComponents/ActionRightPane/index.tsx new file mode 100644 index 0000000000..829ef83385 --- /dev/null +++ b/app/client/src/components/editorComponents/ActionRightPane/index.tsx @@ -0,0 +1,249 @@ +import React, { useMemo } from "react"; +import styled from "styled-components"; +import { Collapse, Classes as BPClasses } from "@blueprintjs/core"; +import Icon, { IconSize } from "components/ads/Icon"; +import { Classes, Variant } from "components/ads/common"; +import Text, { TextType } from "components/ads/Text"; +import { useState } from "react"; +import history from "utils/history"; +import { getTypographyByKey } from "constants/DefaultTheme"; +import Connections from "./Connections"; +import SuggestedWidgets from "./SuggestedWidgets"; +import { ReactNode } from "react"; +import { useEffect } from "react"; +import Button, { Category, Size } from "components/ads/Button"; +import { bindDataOnCanvas } from "../../../actions/actionActions"; +import { useParams } from "react-router"; +import { ExplorerURLParams } from "pages/Editor/Explorer/helpers"; +import { useDispatch, useSelector } from "react-redux"; +import { getWidgets } from "sagas/selectors"; +import AnalyticsUtil from "../../../utils/AnalyticsUtil"; +import { AppState } from "reducers"; +import { getDependenciesFromInverseDependencies } from "../Debugger/helpers"; +import { BUILDER_PAGE_URL } from "constants/routes"; +import { + BACK_TO_CANVAS, + createMessage, + NO_CONNECTIONS, +} from "constants/messages"; +import { + SuggestedWidget, + SuggestedWidget as SuggestedWidgetsType, +} from "api/ActionAPI"; + +const SideBar = styled.div` + padding: ${(props) => props.theme.spaces[0]}px + ${(props) => props.theme.spaces[3]}px ${(props) => props.theme.spaces[4]}px; + overflow: auto; + height: 100%; + width: 100%; + + & > div { + margin-top: ${(props) => props.theme.spaces[11]}px; + } + + .icon-text { + display: flex; + margin-left: ${(props) => props.theme.spaces[2] + 1}px; + + .connection-type { + ${(props) => getTypographyByKey(props, "p1")} + } + } + + .icon-text:nth-child(2) { + padding-top: ${(props) => props.theme.spaces[7]}px; + } + + .description { + ${(props) => getTypographyByKey(props, "p1")} + margin-left: ${(props) => props.theme.spaces[2] + 1}px; + padding-bottom: ${(props) => props.theme.spaces[7]}px; + } +`; + +const Label = styled.span` + cursor: pointer; +`; + +const CollapsibleWrapper = styled.div<{ isOpen: boolean }>` + .${BPClasses.COLLAPSE_BODY} { + padding-top: ${(props) => props.theme.spaces[3]}px; + } + + & > .icon-text:first-child { + color: ${(props) => props.theme.colors.actionSidePane.collapsibleIcon}; + ${(props) => getTypographyByKey(props, "h4")} + cursor: pointer; + .${Classes.ICON} { + ${(props) => !props.isOpen && `transform: rotate(-90deg);`} + } + + .label { + padding-left: ${(props) => props.theme.spaces[1] + 1}px; + } + } +`; + +const SnipingWrapper = styled.div` + ${(props) => getTypographyByKey(props, "p1")} + margin-left: ${(props) => props.theme.spaces[2] + 1}px; + + img { + max-width: 100%; + } + + .image-wrapper { + position: relative; + margin-top: ${(props) => props.theme.spaces[1]}px; + } + + .widget:hover { + cursor: pointer; + } +`; +const Placeholder = styled.div` + display: flex; + justify-content: center; + align-items: center; + flex: 1; + height: 100%; + padding: ${(props) => props.theme.spaces[8]}px; + text-align: center; +`; + +const BackButton = styled.div` + display: flex; + cursor: pointer; + margin-left: ${(props) => props.theme.spaces[1] + 1}px; + .${Classes.TEXT} { + margin-left: ${(props) => props.theme.spaces[4] + 1}px; + } +`; + +type CollapsibleProps = { + expand?: boolean; + children: ReactNode; + label: string; +}; + +export function Collapsible({ + children, + expand = true, + label, +}: CollapsibleProps) { + const [isOpen, setIsOpen] = useState(!!expand); + + useEffect(() => { + setIsOpen(expand); + }, [expand]); + + return ( + + + + {children} + + + ); +} + +function ActionSidebar({ + actionName, + hasResponse, + suggestedWidgets, +}: { + actionName: string; + hasResponse: boolean; + suggestedWidgets?: SuggestedWidgetsType[]; +}) { + const dispatch = useDispatch(); + const widgets = useSelector(getWidgets); + const { applicationId, pageId } = useParams(); + const params = useParams<{ apiId?: string; queryId?: string }>(); + const handleBindData = () => { + AnalyticsUtil.logEvent("SELECT_IN_CANVAS_CLICK", { + actionName: actionName, + apiId: params.apiId || params.queryId, + appId: applicationId, + }); + dispatch( + bindDataOnCanvas({ + queryId: (params.apiId || params.queryId) as string, + applicationId, + pageId, + }), + ); + }; + const hasWidgets = Object.keys(widgets).length > 1; + + const deps = useSelector((state: AppState) => state.evaluations.dependencies); + const entityDependencies = useMemo( + () => + getDependenciesFromInverseDependencies( + deps.inverseDependencyMap, + actionName, + ), + [actionName, deps.inverseDependencyMap], + ); + const hasConnections = + entityDependencies && + (entityDependencies?.directDependencies.length > 0 || + entityDependencies?.inverseDependencies.length > 0); + const showSuggestedWidgets = + hasResponse && suggestedWidgets && !!suggestedWidgets.length; + const showSnipingMode = hasResponse && hasWidgets; + + if (!hasConnections && !showSuggestedWidgets && !showSnipingMode) { + return {createMessage(NO_CONNECTIONS)}; + } + + const navigeteToCanvas = () => { + history.push(BUILDER_PAGE_URL(applicationId, pageId)); + }; + + return ( + + + + {createMessage(BACK_TO_CANVAS)} + + + {hasConnections && ( + + )} + {showSuggestedWidgets && ( + + )} + {hasResponse && Object.keys(widgets).length > 1 && ( + +
Go to canvas and select widgets
+ +