diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/Date_column_types_validation_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/Date_column_types_validation_spec.ts index 0ce8bc321a3..312ebb7c946 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/Date_column_types_validation_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/Date_column_types_validation_spec.ts @@ -43,17 +43,16 @@ describe( // Click unix cell edit table.ClickOnEditIcon(row, column); - // Click on specific date within + // Click on a specific date within the view port of the date picker + // Date picker opens in september 2024 due to the Table data set agHelper.GetNClick( - `${table._dateInputPopover} [aria-label='${table.getFormattedTomorrowDates().verboseFormat}']`, + `${table._dateInputPopover} [aria-label='Thu Sep 26 2024']`, ); - // Check that date is set in column + // Check that the date is set in column table .ReadTableRowColumnData(row, column, "v2") - .then((val) => - expect(val).to.equal(table.getFormattedTomorrowDates().isoFormat), - ); + .then((val) => expect(val).to.equal("2024-09-26")); }; it("1. should allow inline editing of Unix Timestamp in seconds (unix/s)", () => { diff --git a/app/client/cypress/support/Pages/Table.ts b/app/client/cypress/support/Pages/Table.ts index 45dd2132ded..d1439a58927 100644 --- a/app/client/cypress/support/Pages/Table.ts +++ b/app/client/cypress/support/Pages/Table.ts @@ -854,38 +854,4 @@ export class Table { this.agHelper.GetHoverNClick(selector, 1, true); verify && cy.get(selector).eq(1).should("be.disabled"); } - - /** - * Helper function to get formatted date strings for tomorrow's date. - * - * @returns {Object} An object containing: - * - verbose format (e.g., "Sat Sep 21 2024") - * - ISO date format (e.g., "2024-09-21") - */ - public getFormattedTomorrowDates() { - // Create a new Date object for today - const tomorrow = new Date(); - - // Set the date to tomorrow by adding 1 to today's date - tomorrow.setDate(tomorrow.getDate() + 1); - - // Format tomorrow's date in verbose form (e.g., "Sat Sep 21 2024") - const verboseFormat = tomorrow - .toLocaleDateString("en-US", { - weekday: "short", - year: "numeric", - month: "short", - day: "2-digit", - }) - .replace(/,/g, ""); // Remove commas from the formatted string - - // Format tomorrow's date in ISO form (e.g., "2024-09-21") - const isoFormat = tomorrow.toISOString().split("T")[0]; // Extract the date part only - - // Return both formatted date strings as an object - return { - verboseFormat, - isoFormat, - }; - } } diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/AIChat.tsx b/app/client/packages/design-system/widgets/src/components/AIChat/src/AIChat.tsx index d6975d9750a..40a50d43754 100644 --- a/app/client/packages/design-system/widgets/src/components/AIChat/src/AIChat.tsx +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/AIChat.tsx @@ -14,6 +14,7 @@ const _AIChat = (props: AIChatProps, ref: ForwardedRef) => { // assistantName, chatTitle, isWaitingForResponse = false, + onApplyAssistantSuggestion, onPromptChange, onSubmit, prompt, @@ -56,7 +57,12 @@ const _AIChat = (props: AIChatProps, ref: ForwardedRef) => { diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/AssistantSuggestionButton.tsx b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/AssistantSuggestionButton.tsx new file mode 100644 index 00000000000..e28f98c1228 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/AssistantSuggestionButton.tsx @@ -0,0 +1,18 @@ +import { Text } from "@appsmith/wds"; +import { clsx } from "clsx"; +import React from "react"; +import { Button as HeadlessButton } from "react-aria-components"; +import styles from "./styles.module.css"; +import type { AssistantSuggestionButtonProps } from "./types"; + +export const AssistantSuggestionButton = ({ + children, + className, + ...rest +}: AssistantSuggestionButtonProps) => { + return ( + + {children} + + ); +}; diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/index.ts b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/index.ts new file mode 100644 index 00000000000..c9075961d61 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/index.ts @@ -0,0 +1,2 @@ +export * from "./AssistantSuggestionButton"; +export * from "./types"; diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/styles.module.css b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/styles.module.css new file mode 100644 index 00000000000..6aa30119ff7 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/styles.module.css @@ -0,0 +1,20 @@ +.root { + height: 30px; + padding: 0 var(--inner-spacing-4); + background-color: var(--bg-neutral-subtle-alt, #e7e8e8); + border-radius: var(--radius-inner-button, 1.8px); + + &:hover { + background-color: var(--bg-neutral-subtle-alt-hover, #f0f1f1); + } + + &:focus-visible { + box-shadow: + 0 0 0 2px var(--color-bg), + 0 0 0 4px var(--color-bd-focus); + } + + &:active { + background-color: var(--bg-neutral-subtle-alt-active, #e1e2e2); + } +} diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/types.ts b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/types.ts new file mode 100644 index 00000000000..20186f75d24 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/AssistantSuggestionButton/types.ts @@ -0,0 +1,5 @@ +import type { PropsWithChildren } from "react"; +import type { ButtonProps as HeadlessButtonProps } from "react-aria-components"; + +export interface AssistantSuggestionButtonProps + extends PropsWithChildren {} diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/ThreadMessage.tsx b/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/ThreadMessage.tsx index 37ea1f1c792..7b827d3f576 100644 --- a/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/ThreadMessage.tsx +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/ThreadMessage.tsx @@ -1,9 +1,10 @@ -import { Text } from "@appsmith/wds"; +import { Flex, Text } from "@appsmith/wds"; import { clsx } from "clsx"; import React from "react"; import Markdown from "react-markdown"; import SyntaxHighlighter from "react-syntax-highlighter"; import { monokai } from "react-syntax-highlighter/dist/cjs/styles/hljs"; +import { AssistantSuggestionButton } from "../AssistantSuggestionButton"; import { UserAvatar } from "../UserAvatar"; import styles from "./styles.module.css"; import type { ThreadMessageProps } from "./types"; @@ -12,6 +13,8 @@ export const ThreadMessage = ({ className, content, isAssistant, + onApplyAssistantSuggestion, + promptSuggestions = [], username, ...rest }: ThreadMessageProps) => { @@ -50,6 +53,25 @@ export const ThreadMessage = ({ {content} + + {promptSuggestions.length > 0 && ( + + {promptSuggestions.map((suggestion) => ( + onApplyAssistantSuggestion?.(suggestion)} + > + {suggestion} + + ))} + + )} ) : ( <> diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/types.ts b/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/types.ts index 8935dfe1e5c..4459c37a5a9 100644 --- a/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/types.ts +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/ThreadMessage/types.ts @@ -4,4 +4,6 @@ export interface ThreadMessageProps extends HTMLProps { content: string; isAssistant: boolean; username: string; + promptSuggestions?: string[]; + onApplyAssistantSuggestion?: (suggestion: string) => void; } diff --git a/app/client/packages/design-system/widgets/src/components/AIChat/src/types.ts b/app/client/packages/design-system/widgets/src/components/AIChat/src/types.ts index 69572c5d6f8..e27a804b91c 100644 --- a/app/client/packages/design-system/widgets/src/components/AIChat/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/AIChat/src/types.ts @@ -2,6 +2,7 @@ export interface ChatMessage { id: string; content: string; isAssistant: boolean; + promptSuggestions?: string[]; } export interface AIChatProps { @@ -15,4 +16,5 @@ export interface AIChatProps { isWaitingForResponse?: boolean; onPromptChange: (prompt: string) => void; onSubmit?: () => void; + onApplyAssistantSuggestion?: (suggestion: string) => void; } diff --git a/app/client/packages/rts/package.json b/app/client/packages/rts/package.json index e75769c5e5e..53c429b9e2d 100644 --- a/app/client/packages/rts/package.json +++ b/app/client/packages/rts/package.json @@ -15,6 +15,9 @@ "start": "./start-server.sh" }, "dependencies": { + "@opentelemetry/instrumentation-http": "^0.53.0", + "@opentelemetry/sdk-trace-node": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0", "@shared/ast": "workspace:^", "axios": "^1.7.4", "express": "^4.20.0", diff --git a/app/client/packages/rts/src/instrumentation.ts b/app/client/packages/rts/src/instrumentation.ts new file mode 100644 index 00000000000..083c5ca6663 --- /dev/null +++ b/app/client/packages/rts/src/instrumentation.ts @@ -0,0 +1,50 @@ +import { + BatchSpanProcessor, + NodeTracerProvider, +} from "@opentelemetry/sdk-trace-node"; +import { Resource } from "@opentelemetry/resources"; +import { + ATTR_DEPLOYMENT_NAME, + ATTR_SERVICE_INSTANCE_ID, +} from "@opentelemetry/semantic-conventions/incubating"; +import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions"; +import { registerInstrumentations } from "@opentelemetry/instrumentation"; +import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; + +const provider = new NodeTracerProvider({ + resource: new Resource({ + [ATTR_DEPLOYMENT_NAME]: `${process.env.APPSMITH_DEPLOYMENT_NAME || "self-hosted"}`, + [ATTR_SERVICE_INSTANCE_ID]: `${process.env.HOSTNAME || "appsmith-0"}`, + [ATTR_SERVICE_NAME]: "rts", + }), +}); + +const nrTracesExporter = new OTLPTraceExporter({ + url: `${process.env.APPSMITH_NEW_RELIC_OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`, + headers: { + "api-key": `${process.env.APPSMITH_NEW_RELIC_OTLP_LICENSE_KEY}`, + }, +}); + +registerInstrumentations({ + instrumentations: [new HttpInstrumentation()], +}); + +const batchSpanProcessor = new BatchSpanProcessor( + nrTracesExporter, + //Optional BatchSpanProcessor Configurations + { + // The maximum queue size. After the size is reached spans are dropped. + maxQueueSize: 100, + // The maximum batch size of every export. It must be smaller or equal to maxQueueSize. + maxExportBatchSize: 50, + // The interval between two consecutive exports + scheduledDelayMillis: 500, + // How long the export can run before it is cancelled + exportTimeoutMillis: 30000, + }, +); + +provider.addSpanProcessor(batchSpanProcessor); +provider.register(); diff --git a/app/client/packages/rts/src/server.ts b/app/client/packages/rts/src/server.ts index e0b0c3765df..b4d77949d4f 100644 --- a/app/client/packages/rts/src/server.ts +++ b/app/client/packages/rts/src/server.ts @@ -1,3 +1,4 @@ +import "./instrumentation"; import http from "http"; import express from "express"; import { Server } from "socket.io"; diff --git a/app/client/src/PluginActionEditor/PluginActionContext.tsx b/app/client/src/PluginActionEditor/PluginActionContext.tsx index 42c0d4e6745..e37882c7d29 100644 --- a/app/client/src/PluginActionEditor/PluginActionContext.tsx +++ b/app/client/src/PluginActionEditor/PluginActionContext.tsx @@ -28,19 +28,27 @@ interface ChildrenProps { export const PluginActionContextProvider = ( props: ChildrenProps & PluginActionContextType, ) => { - const { action, children, datasource, editorConfig, plugin, settingsConfig } = - props; + const { + action, + actionResponse, + children, + datasource, + editorConfig, + plugin, + settingsConfig, + } = props; // using useMemo to avoid unnecessary renders const contextValue = useMemo( () => ({ action, + actionResponse, datasource, editorConfig, plugin, settingsConfig, }), - [action, datasource, editorConfig, plugin, settingsConfig], + [action, actionResponse, datasource, editorConfig, plugin, settingsConfig], ); return ( diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx index 9c046e9119c..a3065ba2e5e 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx @@ -10,13 +10,17 @@ import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; import Pagination from "pages/Editor/APIEditor/Pagination"; import { reduxForm } from "redux-form"; -import { useHandleRunClick } from "PluginActionEditor/hooks"; +import { + useHandleRunClick, + useAnalyticsOnRunClick, +} from "PluginActionEditor/hooks"; const FORM_NAME = API_EDITOR_FORM_NAME; const APIEditorForm = () => { const { action } = usePluginActionContext(); const { handleRunClick } = useHandleRunClick(); + const { callRunActionAnalytics } = useAnalyticsOnRunClick(); const theme = EditorTheme.LIGHT; const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); @@ -25,6 +29,11 @@ const APIEditorForm = () => { action.userPermissions, ); + const onTestClick = () => { + callRunActionAnalytics(); + handleRunClick(); + }; + return ( { paginationUiComponent={ diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx index f897d38246e..21104245270 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx @@ -1,107 +1,87 @@ -import React, { useCallback, useRef } from "react"; +import React from "react"; import styled from "styled-components"; -import QueryEditor from "pages/Editor/APIEditor/GraphQL/QueryEditor"; -import VariableEditor from "pages/Editor/APIEditor/GraphQL/VariableEditor"; -import useHorizontalResize from "utils/hooks/useHorizontalResize"; -import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; -import classNames from "classnames"; -import { tailwindLayers } from "constants/Layers"; - -const ResizableDiv = styled.div` - display: flex; - height: 100%; - flex-shrink: 0; -`; +import { + CodeEditorBorder, + EditorModes, + EditorSize, + EditorTheme, + TabBehaviour, +} from "components/editorComponents/CodeEditor/EditorConfig"; +import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; +import { Section, Zone } from "pages/Editor/ActionForm"; +import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; +import FormLabel from "components/editorComponents/FormLabel"; const PostBodyContainer = styled.div` - display: flex; - height: 100%; - overflow: hidden; &&&& .CodeMirror { - height: 100%; - border-top: 1px solid var(--ads-v2-color-border); - border-bottom: 1px solid var(--ads-v2-color-border); - border-radius: 0; - padding: 0; - } - & .CodeMirror-scroll { - margin: 0px; - padding: 0px; - overflow: auto !important; + height: auto; + min-height: 250px; } `; -const ResizerHandler = styled.div<{ resizing: boolean }>` - width: 6px; - height: 100%; - margin-left: 2px; - border-right: 1px solid var(--ads-v2-color-border); - background: ${(props) => - props.resizing ? "var(--ads-v2-color-border)" : "transparent"}; - &:hover { - background: var(--ads-v2-color-border); - border-color: transparent; +const StyledFormLabel = styled(FormLabel)` + && { + margin-bottom: var(--ads-v2-spaces-2); + padding: 0; } `; -const DEFAULT_GRAPHQL_VARIABLE_WIDTH = 300; - interface Props { actionName: string; } +const EXPECTED_VARIABLE = { + type: "object", + example: + '{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}', + autocompleteDataType: AutocompleteDataType.OBJECT, +}; + function PostBodyData(props: Props) { const { actionName } = props; const theme = EditorTheme.LIGHT; - const resizeableRef = useRef(null); - const [variableEditorWidth, setVariableEditorWidth] = React.useState( - DEFAULT_GRAPHQL_VARIABLE_WIDTH, - ); - /** - * Variable Editor's resizeable handler for the changing of width - */ - const onVariableEditorWidthChange = useCallback((newWidth) => { - setVariableEditorWidth(newWidth); - }, []); - - const { onMouseDown, onMouseUp, onTouchStart, resizing } = - useHorizontalResize( - resizeableRef, - onVariableEditorWidthChange, - undefined, - true, - ); return ( - -
- -
- - - +
+ +
+ Query + +
+
+ +
+ Query variables + +
+
+
); } diff --git a/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx index cfad25a5ee4..5c0362be319 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx @@ -1,10 +1,16 @@ -import React from "react"; +import React, { useCallback } from "react"; import { IDEToolbar } from "IDE"; import { Button, Menu, MenuContent, MenuTrigger, Tooltip } from "@appsmith/ads"; import { modText } from "utils/helpers"; import { usePluginActionContext } from "../PluginActionContext"; -import { useHandleRunClick } from "PluginActionEditor/hooks"; +import { + useBlockExecution, + useHandleRunClick, + useAnalyticsOnRunClick, +} from "PluginActionEditor/hooks"; import { useToggle } from "@mantine/hooks"; +import { useSelector } from "react-redux"; +import { isActionRunning } from "PluginActionEditor/store"; interface PluginActionToolbarProps { runOptions?: React.ReactNode; @@ -15,7 +21,15 @@ interface PluginActionToolbarProps { const PluginActionToolbar = (props: PluginActionToolbarProps) => { const { action } = usePluginActionContext(); const { handleRunClick } = useHandleRunClick(); + const { callRunActionAnalytics } = useAnalyticsOnRunClick(); const [isMenuOpen, toggleMenuOpen] = useToggle([false, true]); + const blockExecution = useBlockExecution(); + const isRunning = useSelector(isActionRunning(action.id)); + + const onRunClick = useCallback(() => { + callRunActionAnalytics(); + handleRunClick(); + }, [callRunActionAnalytics, handleRunClick]); return ( @@ -27,7 +41,13 @@ const PluginActionToolbar = (props: PluginActionToolbarProps) => { placement="topRight" showArrow={false} > - diff --git a/app/client/src/PluginActionEditor/hooks/index.ts b/app/client/src/PluginActionEditor/hooks/index.ts index 00460d54c4c..c61530a08d8 100644 --- a/app/client/src/PluginActionEditor/hooks/index.ts +++ b/app/client/src/PluginActionEditor/hooks/index.ts @@ -1,3 +1,5 @@ export { useActionSettingsConfig } from "ee/PluginActionEditor/hooks/useActionSettingsConfig"; export { useHandleDeleteClick } from "ee/PluginActionEditor/hooks/useHandleDeleteClick"; export { useHandleRunClick } from "ee/PluginActionEditor/hooks/useHandleRunClick"; +export { useBlockExecution } from "ee/PluginActionEditor/hooks/useBlockExecution"; +export { useAnalyticsOnRunClick } from "ee/PluginActionEditor/hooks/useAnalyticsOnRunClick"; diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index 51a10762d29..22cfe873e30 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -68,25 +68,23 @@ export const fetchPageAction = ( export interface FetchPublishedPageActionPayload { pageId: string; bustCache?: boolean; - firstLoad?: boolean; pageWithMigratedDsl?: FetchPageResponse; } export interface FetchPublishedPageResourcesPayload { pageId: string; + basePageId: string; } export const fetchPublishedPageAction = ( pageId: string, bustCache = false, - firstLoad = false, pageWithMigratedDsl?: FetchPageResponse, ): ReduxAction => ({ type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT, payload: { pageId, bustCache, - firstLoad, pageWithMigratedDsl, }, }); @@ -299,12 +297,14 @@ export const clonePageSuccess = ({ // Fetches resources required for published page, currently only used for fetching actions // In future we can reuse this for fetching other page level resources in published mode -export const fetchPublishedPageResourcesAction = ( - pageId: string, -): ReduxAction => ({ +export const fetchPublishedPageResources = ({ + basePageId, + pageId, +}: FetchPublishedPageResourcesPayload): ReduxAction => ({ type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_RESOURCES_INIT, payload: { pageId, + basePageId, }, }); @@ -675,21 +675,18 @@ export const setupPageAction = ( export interface SetupPublishedPageActionPayload { pageId: string; bustCache: boolean; - firstLoad: boolean; pageWithMigratedDsl?: FetchPageResponse; } export const setupPublishedPage = ( pageId: string, bustCache = false, - firstLoad = false, pageWithMigratedDsl?: FetchPageResponse, ): ReduxAction => ({ type: ReduxActionTypes.SETUP_PUBLISHED_PAGE_INIT, payload: { pageId, bustCache, - firstLoad, pageWithMigratedDsl, }, }); diff --git a/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx b/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx index 18c863ccbc7..4b8f04d18a5 100644 --- a/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx +++ b/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx @@ -17,22 +17,30 @@ import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs"; import { PluginType } from "entities/Action"; import { ApiResponse } from "PluginActionEditor/components/PluginActionResponse/components/ApiResponse"; import { ApiResponseHeaders } from "PluginActionEditor/components/PluginActionResponse/components/ApiResponseHeaders"; -import { noop } from "lodash"; import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; import { getErrorCount } from "selectors/debuggerSelectors"; -import { getPluginActionDebuggerState } from "PluginActionEditor/store"; +import { + getPluginActionDebuggerState, + isActionRunning, +} from "PluginActionEditor/store"; import { doesPluginRequireDatasource } from "ee/entities/Engine/actionHelpers"; import useShowSchema from "components/editorComponents/ActionRightPane/useShowSchema"; import Schema from "components/editorComponents/Debugger/Schema"; import QueryResponseTab from "pages/Editor/QueryEditor/QueryResponseTab"; import type { SourceEntity } from "entities/AppsmithConsole"; import { ENTITY_TYPE as SOURCE_ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils"; -import { useHandleRunClick } from "PluginActionEditor/hooks"; +import { + useBlockExecution, + useHandleRunClick, + useAnalyticsOnRunClick, +} from "PluginActionEditor/hooks"; +import useDebuggerTriggerClick from "components/editorComponents/Debugger/hooks/useDebuggerTriggerClick"; function usePluginActionResponseTabs() { const { action, actionResponse, datasource, plugin } = usePluginActionContext(); const { handleRunClick } = useHandleRunClick(); + const { callRunActionAnalytics } = useAnalyticsOnRunClick(); const IDEViewMode = useSelector(getIDEViewMode); const errorCount = useSelector(getErrorCount); @@ -42,8 +50,17 @@ function usePluginActionResponseTabs() { const { responseTabHeight } = useSelector(getPluginActionDebuggerState); + const onDebugClick = useDebuggerTriggerClick(); + const isRunning = useSelector(isActionRunning(action.id)); + const blockExecution = useBlockExecution(); + const tabs: BottomTab[] = []; + const onRunClick = () => { + callRunActionAnalytics(); + handleRunClick(); + }; + if (IDEViewMode === EditorViewMode.FullScreen) { tabs.push( { @@ -69,9 +86,9 @@ function usePluginActionResponseTabs() { @@ -83,10 +100,10 @@ function usePluginActionResponseTabs() { panelComponent: ( ), }, @@ -132,8 +149,8 @@ function usePluginActionResponseTabs() { actionName={action.name} actionSource={actionSource} currentActionConfig={action} - isRunning={false} - onRunClick={handleRunClick} + isRunning={isRunning} + onRunClick={onRunClick} runErrorMessage={""} // TODO /> ), diff --git a/app/client/src/ce/PluginActionEditor/hooks/useAnalyticsOnRunClick.ts b/app/client/src/ce/PluginActionEditor/hooks/useAnalyticsOnRunClick.ts new file mode 100644 index 00000000000..421b9ebcc63 --- /dev/null +++ b/app/client/src/ce/PluginActionEditor/hooks/useAnalyticsOnRunClick.ts @@ -0,0 +1,33 @@ +import { useCallback } from "react"; +import { useSelector } from "react-redux"; +import { usePluginActionContext } from "PluginActionEditor/PluginActionContext"; +import { getPageNameByPageId } from "ee/selectors/entitiesSelector"; +import AnalyticsUtil from "ee/utils/AnalyticsUtil"; + +function useAnalyticsOnRunClick() { + const { action, datasource, plugin } = usePluginActionContext(); + const pageName = useSelector((state) => + getPageNameByPageId(state, action.pageId), + ); + + const actionId = action.id; + const actionName = action.name; + const datasourceId = datasource?.id; + const pluginName = plugin.name; + const isMock = !!datasource?.isMock || false; // as mock db exists only for postgres and mongo plugins + + const callRunActionAnalytics = useCallback(() => { + AnalyticsUtil.logEvent("RUN_ACTION_CLICK", { + actionId, + actionName, + datasourceId, + pageName, + pluginName, + isMock, + }); + }, [actionId, actionName, datasourceId, pageName, pluginName, isMock]); + + return { callRunActionAnalytics }; +} + +export { useAnalyticsOnRunClick }; diff --git a/app/client/src/ce/PluginActionEditor/hooks/useBlockExecution.ts b/app/client/src/ce/PluginActionEditor/hooks/useBlockExecution.ts new file mode 100644 index 00000000000..e02bcc7231d --- /dev/null +++ b/app/client/src/ce/PluginActionEditor/hooks/useBlockExecution.ts @@ -0,0 +1,63 @@ +import { getHasExecuteActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; +import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { DEFAULT_DATASOURCE_NAME } from "constants/ApiEditorConstants/ApiEditorConstants"; +import { UIComponentTypes } from "api/PluginApi"; +import { SQL_DATASOURCES } from "constants/QueryEditorConstants"; +import { usePluginActionContext } from "PluginActionEditor/PluginActionContext"; + +const useBlockExecution = () => { + const { action, plugin } = usePluginActionContext(); + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const isExecutePermitted = getHasExecuteActionPermission( + isFeatureEnabled, + action?.userPermissions, + ); + + let actionBody = ""; + let blockExecution = false; + + // API Editor Constants + // this gets the url of the current action's datasource + const actionDatasourceUrl = + action.datasource.datasourceConfiguration?.url || ""; + const actionDatasourceUrlPath = action.actionConfiguration.path || ""; + // this gets the name of the current action's datasource + const actionDatasourceName = action.datasource.name || ""; + + // Query Editor Constants + if (!!action.actionConfiguration) { + if ("formData" in action.actionConfiguration) { + // if the action has a formData (the action is postUQI e.g. Oracle) + actionBody = action.actionConfiguration.formData?.body?.data || ""; + } else { + // if the action is pre UQI, the path is different e.g. mySQL + actionBody = action.actionConfiguration?.body || ""; + } + } + + if ( + [ + UIComponentTypes.ApiEditorForm, + UIComponentTypes.GraphQLEditorForm, + ].includes(plugin.uiComponent) + ) { + // if the url is empty and the action's datasource name is the default datasource name (this means the api does not have a datasource attached) + // or the user does not have permission, + // we block action execution. + blockExecution = + (!actionDatasourceUrl && + !actionDatasourceUrlPath && + actionDatasourceName === DEFAULT_DATASOURCE_NAME) || + !isExecutePermitted; + } else { + // if (the body is empty and the action is an sql datasource) or the user does not have permission, block action execution. + blockExecution = + (!actionBody && SQL_DATASOURCES.includes(plugin.name)) || + !isExecutePermitted; + } + + return blockExecution; +}; + +export { useBlockExecution }; diff --git a/app/client/src/ce/PluginActionEditor/hooks/useHandleRunClick.ts b/app/client/src/ce/PluginActionEditor/hooks/useHandleRunClick.ts index b44c80f5c61..62d8075bcbd 100644 --- a/app/client/src/ce/PluginActionEditor/hooks/useHandleRunClick.ts +++ b/app/client/src/ce/PluginActionEditor/hooks/useHandleRunClick.ts @@ -1,8 +1,8 @@ +import { useCallback } from "react"; +import { useDispatch } from "react-redux"; import { runAction } from "actions/pluginActionActions"; import type { PaginationField } from "api/ActionAPI"; import { usePluginActionContext } from "PluginActionEditor/PluginActionContext"; -import { useCallback } from "react"; -import { useDispatch } from "react-redux"; function useHandleRunClick() { const { action } = usePluginActionContext(); diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 5d9a68d41c5..3d344202b8a 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -985,6 +985,8 @@ const AppViewActionTypes = { SET_APP_VIEWER_HEADER_HEIGHT: "SET_APP_VIEWER_HEADER_HEIGHT", SET_APP_SIDEBAR_PINNED: "SET_APP_SIDEBAR_PINNED", FETCH_PUBLISHED_PAGE_RESOURCES_INIT: "FETCH_PUBLISHED_PAGE_RESOURCES_INIT", + FETCH_PUBLISHED_PAGE_RESOURCES_SUCCESS: + "FETCH_PUBLISHED_PAGE_RESOURCES_SUCCESS", }; const AppViewActionErrorTypes = { diff --git a/app/client/src/ce/sagas/PageSagas.tsx b/app/client/src/ce/sagas/PageSagas.tsx index 12e52ba930c..aeee2a7b60c 100644 --- a/app/client/src/ce/sagas/PageSagas.tsx +++ b/app/client/src/ce/sagas/PageSagas.tsx @@ -325,12 +325,47 @@ export function* fetchPageSaga(action: ReduxAction) { } } +export function* updateCanvasLayout(response: FetchPageResponse) { + // Wait for widget config to load before we can get the canvas payload + yield call(waitForWidgetConfigBuild); + // Get Canvas payload + const canvasWidgetsPayload = getCanvasWidgetsPayload(response); + + // resize main canvas + resizePublishedMainCanvasToLowestWidget(canvasWidgetsPayload.widgets); + // Update the canvas + yield put(initCanvasLayout(canvasWidgetsPayload)); + + // Since new page has new layout, we need to generate a data structure + // to compute dynamic height based on the new layout. + yield put(generateAutoHeightLayoutTreeAction(true, true)); +} + +export function* postFetchedPublishedPage( + response: FetchPageResponse, + pageId: string, +) { + // set current page + yield put( + updateCurrentPage( + pageId, + response.data.slug, + response.data.userPermissions, + ), + ); + // Clear any existing caches + yield call(clearEvalCache); + // Set url params + yield call(setDataUrl); + + yield call(updateCanvasLayout, response); +} + export function* fetchPublishedPageSaga( action: ReduxAction, ) { try { - const { bustCache, firstLoad, pageId, pageWithMigratedDsl } = - action.payload; + const { bustCache, pageId, pageWithMigratedDsl } = action.payload; const params = { pageId, bustCache }; const response: FetchPageResponse = yield call( @@ -342,41 +377,9 @@ export function* fetchPublishedPageSaga( const isValidResponse: boolean = yield validateResponse(response); if (isValidResponse) { - // Clear any existing caches - yield call(clearEvalCache); - // Set url params - yield call(setDataUrl); - // Wait for widget config to load before we can get the canvas payload - yield call(waitForWidgetConfigBuild); - // Get Canvas payload - const canvasWidgetsPayload = getCanvasWidgetsPayload(response); - - // resize main canvas - resizePublishedMainCanvasToLowestWidget(canvasWidgetsPayload.widgets); - // Update the canvas - yield put(initCanvasLayout(canvasWidgetsPayload)); - // set current page - yield put( - updateCurrentPage( - pageId, - response.data.slug, - response.data.userPermissions, - ), - ); + yield call(postFetchedPublishedPage, response, pageId); - // dispatch fetch page success yield put(fetchPublishedPageSuccess()); - - // Since new page has new layout, we need to generate a data structure - // to compute dynamic height based on the new layout. - yield put(generateAutoHeightLayoutTreeAction(true, true)); - - /* Currently, All Actions are fetched in initSagas and on pageSwitch we only fetch page - */ - // Hence, if is not isFirstLoad then trigger evaluation with execute pageLoad action - if (!firstLoad) { - yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); - } } } catch (error) { yield put({ @@ -392,9 +395,9 @@ export function* fetchPublishedPageResourcesSaga( action: ReduxAction, ) { try { - const { pageId } = action.payload; + const { basePageId, pageId } = action.payload; - const params = { defaultPageId: pageId }; + const params = { defaultPageId: basePageId }; const initConsolidatedApiResponse: ApiResponse = yield ConsolidatedPageLoadApi.getConsolidatedPageLoadDataView(params); @@ -410,10 +413,18 @@ export function* fetchPublishedPageResourcesSaga( // In future, we can reuse this saga to fetch other resources of the page like actionCollections etc const { publishedActions } = response; - // Sending applicationId as empty as we have publishedActions present, - // it won't call the actions view api with applicationId + yield call( + postFetchedPublishedPage, + response.pageWithMigratedDsl, + pageId, + ); + + // NOTE: fetchActionsForView is used here to update publishedActions in redux store and not to fetch actions again yield put(fetchActionsForView({ applicationId: "", publishedActions })); yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); + yield put({ + type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_RESOURCES_SUCCESS, + }); } } catch (error) { yield put({ @@ -1425,21 +1436,13 @@ export function* setupPublishedPageSaga( action: ReduxAction, ) { try { - const { bustCache, firstLoad, pageId, pageWithMigratedDsl } = - action.payload; + const { bustCache, pageId, pageWithMigratedDsl } = action.payload; /* Added the first line for isPageSwitching redux state to be true when page is being fetched to fix scroll position issue. Added the second line for sync call instead of async (due to first line) as it was leading to issue with on page load actions trigger. */ - yield put( - fetchPublishedPageAction( - pageId, - bustCache, - firstLoad, - pageWithMigratedDsl, - ), - ); + yield put(fetchPublishedPageAction(pageId, bustCache, pageWithMigratedDsl)); yield take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS); yield put({ diff --git a/app/client/src/ce/sagas/__tests__/PageSaga.test.ts b/app/client/src/ce/sagas/__tests__/PageSaga.test.ts index 96dc534cc27..4fb55f74d31 100644 --- a/app/client/src/ce/sagas/__tests__/PageSaga.test.ts +++ b/app/client/src/ce/sagas/__tests__/PageSaga.test.ts @@ -47,7 +47,6 @@ describe("ce/PageSaga", () => { pageWithMigratedDsl: mockResponse.data .pageWithMigratedDsl as FetchPageResponse, bustCache: false, - firstLoad: true, }, }; @@ -57,7 +56,6 @@ describe("ce/PageSaga", () => { fetchPublishedPageAction( action.payload.pageId, action.payload.bustCache, - action.payload.firstLoad, action.payload.pageWithMigratedDsl, ), ) diff --git a/app/client/src/ce/utils/analyticsUtilTypes.ts b/app/client/src/ce/utils/analyticsUtilTypes.ts index ec84c03efdf..9bb8ea464f5 100644 --- a/app/client/src/ce/utils/analyticsUtilTypes.ts +++ b/app/client/src/ce/utils/analyticsUtilTypes.ts @@ -48,9 +48,7 @@ export type EventName = | "DELETE_SAAS" | "RUN_SAAS_API" | "SAVE_API_CLICK" - | "RUN_API" | "RUN_API_CLICK" - | "RUN_API_SHORTCUT" | "DELETE_API" | "IMPORT_API" | "EXPAND_API" @@ -59,9 +57,8 @@ export type EventName = | "ADD_API_PAGE" | "DUPLICATE_ACTION" | "DUPLICATE_ACTION_CLICK" - | "RUN_QUERY" | "RUN_QUERY_CLICK" - | "RUN_QUERY_SHORTCUT" + | "RUN_ACTION_CLICK" | "DELETE_QUERY" | "MOVE_API" | "3P_PROVIDER_CLICK" diff --git a/app/client/src/components/editorComponents/Debugger/LogItem.tsx b/app/client/src/components/editorComponents/Debugger/LogItem.tsx index 821002cb03d..b80d94c4fec 100644 --- a/app/client/src/components/editorComponents/Debugger/LogItem.tsx +++ b/app/client/src/components/editorComponents/Debugger/LogItem.tsx @@ -395,4 +395,4 @@ function LogItem(props: LogItemProps) { ); } -export default LogItem; +export default React.memo(LogItem); diff --git a/app/client/src/components/propertyControls/ArrayComponent.tsx b/app/client/src/components/propertyControls/ArrayComponent.tsx new file mode 100644 index 00000000000..ec98932273b --- /dev/null +++ b/app/client/src/components/propertyControls/ArrayComponent.tsx @@ -0,0 +1,152 @@ +import { Button } from "@appsmith/ads"; +import { debounce } from "lodash"; +import React, { useCallback, useEffect, useState } from "react"; +import styled from "styled-components"; +import { ControlWrapper, InputGroup } from "./StyledControls"; + +function updateOptionLabel( + items: Array, + index: number, + updatedLabel: string, +) { + return items.map((option: T, optionIndex) => { + if (index !== optionIndex) { + return option; + } + + return updatedLabel; + }); +} + +const StyledBox = styled.div` + width: 10px; +`; + +type UpdateItemsFunction = ( + items: string[], + isUpdatedViaKeyboard?: boolean, +) => void; + +interface ArrayComponentProps { + items: string[]; + updateItems: UpdateItemsFunction; + addLabel?: string; +} + +const StyledInputGroup = styled(InputGroup)` + > .ads-v2-input__input-section > div { + flex: 1; + min-width: 0px; + } +`; + +export function ArrayComponent(props: ArrayComponentProps) { + const [renderItems, setRenderItems] = useState([]); + const [typing, setTyping] = useState(false); + const { items } = props; + + useEffect(() => { + let { items } = props; + + items = Array.isArray(items) ? items.slice() : []; + + items.length !== 0 && !typing && setRenderItems(items); + }, [props, items.length, renderItems.length, typing]); + + const debouncedUpdateItems = useCallback( + debounce((updatedItems: string[]) => { + props.updateItems(updatedItems, true); + }, 200), + [props.updateItems], + ); + + function updateKey(index: number, updatedKey: string) { + let { items } = props; + + items = Array.isArray(items) ? items : []; + const updatedItems = updateOptionLabel(items, index, updatedKey); + const updatedRenderItems = updateOptionLabel( + renderItems, + index, + updatedKey, + ); + + setRenderItems(updatedRenderItems); + debouncedUpdateItems(updatedItems); + } + + function deleteItem(index: number, isUpdatedViaKeyboard = false) { + let { items } = props; + + items = Array.isArray(items) ? items : []; + + const newItems = items.filter((o, i) => i !== index); + const newRenderItems = renderItems.filter((o, i) => i !== index); + + setRenderItems(newRenderItems); + props.updateItems(newItems, isUpdatedViaKeyboard); + } + + function addItem(e: React.MouseEvent) { + let { items } = props; + + items = Array.isArray(items) ? items.slice() : []; + + items.push(""); + + const updatedRenderItems = renderItems.slice(); + + updatedRenderItems.push(""); + + setRenderItems(updatedRenderItems); + props.updateItems(items, e.detail === 0); + } + + function onInputFocus() { + setTyping(true); + } + + function onInputBlur() { + setTyping(false); + } + + return ( + <> + {renderItems.map((item: string, index) => { + return ( + + updateKey(index, value)} + onFocus={onInputFocus} + value={item} + /> + + + + + ); +} diff --git a/app/client/src/components/propertyControls/ArrayControl.tsx b/app/client/src/components/propertyControls/ArrayControl.tsx new file mode 100644 index 00000000000..8dab080e041 --- /dev/null +++ b/app/client/src/components/propertyControls/ArrayControl.tsx @@ -0,0 +1,48 @@ +import { objectKeys } from "@appsmith/utils"; +import type { DropdownOption } from "components/constants"; +import React from "react"; +import { isDynamicValue } from "utils/DynamicBindingUtils"; +import { ArrayComponent } from "./ArrayComponent"; +import type { ControlData, ControlProps } from "./BaseControl"; +import BaseControl from "./BaseControl"; + +class ArrayControl extends BaseControl { + render() { + return ( + + ); + } + + updateItems = (items: string[], isUpdatedViaKeyboard = false) => { + this.updateProperty(this.props.propertyName, items, isUpdatedViaKeyboard); + }; + + static getControlType() { + return "ARRAY_INPUT"; + } + + static canDisplayValueInUI(_config: ControlData, value: string): boolean { + if (isDynamicValue(value)) return false; + + try { + const items: DropdownOption[] = JSON.parse(value); + + for (const x of items) { + const keys = objectKeys(x); + + if (!keys.includes("label") || !keys.includes("value")) { + return false; + } + } + } catch { + return false; + } + + return true; + } +} + +export default ArrayControl; diff --git a/app/client/src/components/propertyControls/index.ts b/app/client/src/components/propertyControls/index.ts index 6683cd30246..cf85e900e04 100644 --- a/app/client/src/components/propertyControls/index.ts +++ b/app/client/src/components/propertyControls/index.ts @@ -76,12 +76,14 @@ import type { IconSelectControlV2Props } from "./IconSelectControlV2"; import IconSelectControlV2 from "./IconSelectControlV2"; import PrimaryColumnsControlWDS from "./PrimaryColumnsControlWDS"; import ToolbarButtonListControl from "./ToolbarButtonListControl"; +import ArrayControl from "./ArrayControl"; export const PropertyControls = { InputTextControl, DropDownControl, SwitchControl, OptionControl, + ArrayControl, CodeEditorControl, DatePickerControl, ActionSelectorControl, diff --git a/app/client/src/ee/PluginActionEditor/hooks/useAnalyticsOnRunClick.ts b/app/client/src/ee/PluginActionEditor/hooks/useAnalyticsOnRunClick.ts new file mode 100644 index 00000000000..c77f70fc9d9 --- /dev/null +++ b/app/client/src/ee/PluginActionEditor/hooks/useAnalyticsOnRunClick.ts @@ -0,0 +1 @@ +export * from "ce/PluginActionEditor/hooks/useAnalyticsOnRunClick"; diff --git a/app/client/src/ee/PluginActionEditor/hooks/useBlockExecution.ts b/app/client/src/ee/PluginActionEditor/hooks/useBlockExecution.ts new file mode 100644 index 00000000000..d95708182c3 --- /dev/null +++ b/app/client/src/ee/PluginActionEditor/hooks/useBlockExecution.ts @@ -0,0 +1 @@ +export * from "ce/PluginActionEditor/hooks/useBlockExecution"; diff --git a/app/client/src/entities/Engine/AppViewerEngine.ts b/app/client/src/entities/Engine/AppViewerEngine.ts index d93d7b6cb1a..e4cca1b7db6 100644 --- a/app/client/src/entities/Engine/AppViewerEngine.ts +++ b/app/client/src/entities/Engine/AppViewerEngine.ts @@ -105,7 +105,7 @@ export default class AppViewerEngine extends AppEngine { }), fetchSelectedAppThemeAction(applicationId, currentTheme), fetchAppThemesAction(applicationId, themes), - setupPublishedPage(toLoadPageId, true, true, pageWithMigratedDsl), + setupPublishedPage(toLoadPageId, true, pageWithMigratedDsl), ]; const successActionEffects = [ diff --git a/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/defaultConfig.ts b/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/defaultConfig.ts index 6d618658a41..77e0374f409 100644 --- a/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/defaultConfig.ts +++ b/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/defaultConfig.ts @@ -7,4 +7,6 @@ export const defaultsConfig = { widgetType: "AI_CHAT", version: 1, responsiveBehavior: ResponsiveBehavior.Fill, + initialAssistantMessage: "", + initialAssistantSuggestions: [], } as unknown as WidgetDefaultProps; diff --git a/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/propertyPaneContent.ts b/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/propertyPaneContent.ts index ac4abc2c8b1..8fff6583b8d 100644 --- a/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/propertyPaneContent.ts +++ b/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/config/propertyPaneContent.ts @@ -79,16 +79,27 @@ export const propertyPaneContent = [ defaultValue: "", }, { - helpText: "Configures a prompt for the assistant", - propertyName: "systemPrompt", - label: "Prompt", + helpText: "Configures an initial assistant message", + propertyName: "initialAssistantMessage", + label: "Initial Assistant Message", controlType: "INPUT_TEXT", - isJSConvertible: false, - isBindProperty: false, + isJSConvertible: true, + isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, defaultValue: "", }, + { + helpText: "Configures initial assistant suggestions", + propertyName: "initialAssistantSuggestions", + label: "Initial Assistant Suggestions", + controlType: "ARRAY_INPUT", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.ARRAY }, + defaultValue: [], + }, { helpText: "Controls the visibility of the widget", propertyName: "isVisible", diff --git a/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/index.tsx b/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/index.tsx index f94fb5935f7..0c2d773abae 100644 --- a/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/index.tsx +++ b/app/client/src/modules/ui-builder/ui/wds/WDSAIChatWidget/widget/index.tsx @@ -27,10 +27,12 @@ import { export interface WDSAIChatWidgetProps extends ContainerWidgetProps {} + export interface Message { id: string; content: string; role: "assistant" | "user" | "system"; + promptSuggestions?: string[]; } interface State extends WidgetState { @@ -43,24 +45,7 @@ class WDSAIChatWidget extends BaseWidget { static type = "WDS_AI_CHAT_WIDGET"; state = { - messages: [ - { - id: "1", - content: "Hello! How can I help you?", - role: "assistant" as const, - }, - { - id: "2", - content: "Find stuck support requests", - role: "user" as const, - }, - { - id: "3", - content: - "I'm finding these customer support requests that have been waiting for a response for over a day:", - role: "assistant" as const, - }, - ], + messages: [] as Message[], prompt: "", isWaitingForResponse: false, }; @@ -123,13 +108,85 @@ class WDSAIChatWidget extends BaseWidget { return {}; } - adaptMessages(messages: Message[]): ChatMessage[] { - return messages.map((message) => ({ - ...message, - isAssistant: message.role === "assistant", - })); + componentDidMount() { + // Add initial assistant message with suggestions if they were configured + if (this.props.initialAssistantMessage.length > 0) { + this.setState((state) => ({ + ...state, + messages: [ + { + id: Math.random().toString(), + content: this.props.initialAssistantMessage, + role: "assistant", + promptSuggestions: this.props.initialAssistantSuggestions || [], + }, + ], + })); + } + } + + componentDidUpdate(prevProps: WDSAIChatWidgetProps): void { + // Track changes in the widget's properties and update the local state accordingly + + // Update the initial assistant message + if ( + prevProps.initialAssistantMessage !== + this.props.initialAssistantMessage || + prevProps.initialAssistantSuggestions !== + this.props.initialAssistantSuggestions + ) { + let updatedMessage: Message | null; + + // + if (this.props.initialAssistantMessage.length > 0) { + const currentMessage = this.state.messages[0]; + + updatedMessage = { + // If the initial assistant message is set, update it + // Otherwise, create a new one + ...(currentMessage || { + id: Math.random().toString(), + role: "assistant", + }), + content: this.props.initialAssistantMessage, + promptSuggestions: this.props.initialAssistantSuggestions, + }; + } else { + updatedMessage = null; + } + + this.setState((state) => ({ + ...state, + messages: updatedMessage ? [updatedMessage] : [], + })); + } } + updatePrompt = (prompt: string) => { + this.setState({ prompt }); + }; + + adaptMessages = (messages: Message[]): ChatMessage[] => { + const chatMessages: ChatMessage[] = messages.map((message) => { + if (message.role === "assistant") { + return { + id: message.id, + content: message.content, + isAssistant: true, + promptSuggestions: message.promptSuggestions || [], + }; + } + + return { + id: message.id, + content: message.content, + isAssistant: false, + }; + }); + + return chatMessages; + }; + handleMessageSubmit = (event?: FormEvent) => { event?.preventDefault(); @@ -148,18 +205,7 @@ class WDSAIChatWidget extends BaseWidget { }), () => { const messages: Message[] = [...this.state.messages]; - - if (this.props.systemPrompt) { - messages.unshift({ - id: String(Date.now()), - content: this.props.systemPrompt, - role: "system", - }); - } - - const params = { - messages, - }; + const params = { messages }; this.executeAction({ triggerPropertyName: "onClick", @@ -182,6 +228,8 @@ class WDSAIChatWidget extends BaseWidget { id: Math.random().toString(), content: this.props.queryData.choices[0].message.content, role: "assistant", + // TODO: Add prompt suggestions from the query data, if any + promptSuggestions: [], }, ], isWaitingForResponse: false, @@ -190,7 +238,11 @@ class WDSAIChatWidget extends BaseWidget { }; handlePromptChange = (prompt: string) => { - this.setState({ prompt }); + this.updatePrompt(prompt); + }; + + handleApplyAssistantSuggestion = (suggestion: string) => { + this.updatePrompt(suggestion); }; getWidgetView(): ReactNode { @@ -199,6 +251,7 @@ class WDSAIChatWidget extends BaseWidget { assistantName={this.props.assistantName} chatTitle={this.props.chatTitle} isWaitingForResponse={this.state.isWaitingForResponse} + onApplyAssistantSuggestion={this.handleApplyAssistantSuggestion} onPromptChange={this.handlePromptChange} onSubmit={this.handleMessageSubmit} prompt={this.state.prompt} diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index bde4699342c..abe1ebbf388 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -28,10 +28,7 @@ import { useSelector } from "react-redux"; import BrandingBadge from "./BrandingBadge"; import { setAppViewHeaderHeight } from "actions/appViewActions"; import { CANVAS_SELECTOR } from "constants/WidgetConstants"; -import { - setupPublishedPage, - fetchPublishedPageResourcesAction, -} from "actions/pageActions"; +import { fetchPublishedPageResources } from "actions/pageActions"; import usePrevious from "utils/hooks/usePrevious"; import { getIsBranchUpdated } from "../utils"; import { APP_MODE } from "entities/App"; @@ -165,10 +162,12 @@ function AppViewer(props: Props) { )?.pageId; if (pageId) { - dispatch(setupPublishedPage(pageId, true)); - - // Used for fetching page resources - dispatch(fetchPublishedPageResourcesAction(basePageId)); + dispatch( + fetchPublishedPageResources({ + basePageId, + pageId, + }), + ); } } } diff --git a/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx index 71baf3d547d..80a1748ad0c 100644 --- a/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx @@ -111,6 +111,7 @@ const Wrapper = styled.div` flex-direction: row; height: 100%; position: relative; + overflow: hidden; `; const MainContainer = styled.div` diff --git a/app/client/src/pages/Editor/APIEditor/Editor.tsx b/app/client/src/pages/Editor/APIEditor/Editor.tsx index 5ce681b23e0..1e6fab13010 100644 --- a/app/client/src/pages/Editor/APIEditor/Editor.tsx +++ b/app/client/src/pages/Editor/APIEditor/Editor.tsx @@ -229,9 +229,10 @@ class ApiEditor extends React.Component { const formStyles: CSSProperties = { position: "relative", - height: "100%", display: "flex", flexDirection: "column", + flexGrow: "1", + overflow: "auto", }; // TODO: Fix this the next time the file is edited diff --git a/app/client/src/pages/Editor/APIEditor/GraphQL/QueryEditor.tsx b/app/client/src/pages/Editor/APIEditor/GraphQL/QueryEditor.tsx deleted file mode 100644 index 53948e1520b..00000000000 --- a/app/client/src/pages/Editor/APIEditor/GraphQL/QueryEditor.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from "react"; -import { Field } from "redux-form"; -// // Codemirror GraphQL plugins -import "codemirror-graphql/hint"; -import "codemirror-graphql/info"; -import "codemirror-graphql/jump"; -import "codemirror-graphql/mode"; - -import QueryWrapper from "./QueryWrapperWithCSS"; -import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; -import { - CodeEditorBorder, - EditorModes, - EditorSize, - TabBehaviour, -} from "components/editorComponents/CodeEditor/EditorConfig"; -import styled from "styled-components"; -import { Text, TextType } from "@appsmith/ads-old"; -import LazyCodeEditor from "components/editorComponents/LazyCodeEditor"; - -const QueryHeader = styled(Text)` - background: var(--ads-v2-color-bg-subtle); - padding: 8px 16px; -`; - -interface QueryProps { - // Path to store the value in the actual data object - dataTreePath: string; - // Height for the editor - height: string; - // Name of the field of the form - name: string; - // Theme to be used in CodeEditor - theme: EditorTheme; -} - -/** - * Query Editor is for writing Graphql query using the Codemirror Editor which we use - * @param props Props that are required by the CodeEditor to render the query editor - * @returns Component with Editor - */ -function QueryEditor(props: QueryProps) { - const editorProps = { - mode: EditorModes.GRAPHQL_WITH_BINDING, - tabBehaviour: TabBehaviour.INDENT, - size: EditorSize.EXTENDED, - showLineNumbers: true, - }; - - return ( - - - Query - - - - ); -} - -export default QueryEditor; diff --git a/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx b/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx deleted file mode 100644 index 89a3d3b96d3..00000000000 --- a/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from "react"; -import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; -import { - CodeEditorBorder, - EditorModes, - EditorSize, - TabBehaviour, -} from "components/editorComponents/CodeEditor/EditorConfig"; -import styled from "styled-components"; -import { Text, TextType } from "@appsmith/ads-old"; -import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; -import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; - -const VariableWrapper = styled.div` - display: flex; - flex-direction: column; - width: 100%; - flex-shrink: 0; - &&&&& .CodeMirror { - border: 0px; - } - &&& .CodeMirror-gutters { - background: var(--ads-v2-color-bg-subtle); - } -`; - -const VariableHeader = styled(Text)` - background: var(--ads-v2-color-bg-subtle); - padding: 8px 16px; -`; - -const EXPECTED_VARIABLE = { - type: "object", - example: - '{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}', - autocompleteDataType: AutocompleteDataType.OBJECT, -}; - -interface VariableProps { - // Name of the action to define the path to the config property - actionName: string; - // Theme to be used in CodeEditor - theme: EditorTheme; -} - -/** - * Variable Editor is for writing Graphql variables using the Codemirror Editor which we use for JSON - * @param props Props that are required by the CodeEditor to render the variable editor - * @returns Component with Editor - */ -function VariableEditor(props: VariableProps) { - return ( - - - Query variables - - - - ); -} - -export default VariableEditor; diff --git a/app/client/src/pages/Editor/ActionForm/Section/index.tsx b/app/client/src/pages/Editor/ActionForm/Section/index.tsx index 937ad0b631d..7b664e11960 100644 --- a/app/client/src/pages/Editor/ActionForm/Section/index.tsx +++ b/app/client/src/pages/Editor/ActionForm/Section/index.tsx @@ -5,11 +5,13 @@ import styles from "./styles.module.css"; interface SectionProps extends React.HTMLAttributes { children: React.ReactNode; isStandalone?: boolean; + isFullWidth?: boolean; } const Section: React.FC = ({ children, className, + isFullWidth = false, isStandalone = false, ...props }) => { @@ -18,6 +20,7 @@ const Section: React.FC = ({ return (
diff --git a/app/client/src/pages/Editor/ActionForm/Section/styles.module.css b/app/client/src/pages/Editor/ActionForm/Section/styles.module.css index e3d2a2d2f30..fb03da7ffbd 100644 --- a/app/client/src/pages/Editor/ActionForm/Section/styles.module.css +++ b/app/client/src/pages/Editor/ActionForm/Section/styles.module.css @@ -13,4 +13,8 @@ &[data-standalone="false"]:not(:last-child) { border-bottom: 1px solid var(--ads-v2-color-border); } + + &[data-fullwidth="true"] { + max-width: none; + } } diff --git a/app/client/src/reducers/uiReducers/appViewReducer.tsx b/app/client/src/reducers/uiReducers/appViewReducer.tsx index 1d3671392f2..05ffcdbfcad 100644 --- a/app/client/src/reducers/uiReducers/appViewReducer.tsx +++ b/app/client/src/reducers/uiReducers/appViewReducer.tsx @@ -26,6 +26,11 @@ const appViewReducer = createReducer(initialState, { [ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT]: (state: AppViewReduxState) => { return { ...state, isFetchingPage: true }; }, + [ReduxActionTypes.FETCH_PUBLISHED_PAGE_RESOURCES_INIT]: ( + state: AppViewReduxState, + ) => { + return { ...state, isFetchingPage: true }; + }, [ReduxActionErrorTypes.FETCH_PUBLISHED_PAGE_ERROR]: ( state: AppViewReduxState, ) => { @@ -44,6 +49,14 @@ const appViewReducer = createReducer(initialState, { isFetchingPage: false, }; }, + [ReduxActionTypes.FETCH_PUBLISHED_PAGE_RESOURCES_SUCCESS]: ( + state: AppViewReduxState, + ) => { + return { + ...state, + isFetchingPage: false, + }; + }, [ReduxActionTypes.SET_APP_VIEWER_HEADER_HEIGHT]: ( state: AppViewReduxState, action: ReduxAction, diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts index 3227e290ecc..929aa0a3638 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts @@ -277,6 +277,11 @@ describe("evaluateAndGenerateResponse", () => { [], [], ); + + expect(webworkerResponse.workerResponse.dependencies).toEqual({ + "Text1.text": ["Text2.text", "Text1"], + "Text2.text": ["Text2"], + }); const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts index 8b75815c5ba..bc561de0df5 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts @@ -139,6 +139,7 @@ export const evaluateAndGenerateResponse = ( ); defaultResponse.staleMetaIds = updateResponse.staleMetaIds; + defaultResponse.dependencies = dataTreeEvaluator.inverseDependencies; // when additional paths are required to be added as updates, we extract the updates from the data tree using these paths. const additionalUpdates = getNewDataTreeUpdates( diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 65539a33a99..c97f664a5bc 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -4769,6 +4769,15 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api-logs@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/api-logs@npm:0.53.0" + dependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 3383ff75f94a77402370a655f8edf049f9864ad60140f70821a1b775ce43bdb9ca6fade533a1faf46dbca19f3189bcbf1f8805062f5a68bfe2a00281b1712d1f + languageName: node + linkType: hard + "@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" @@ -4776,6 +4785,15 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/context-async-hooks@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/context-async-hooks@npm:1.26.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: f0fe5bfa3aeed99fbe7d6f6157e3bcc2e4450850a62ef60e551812f3e5aa72cb81e38de8c4e1b6934c93e18579a503664597f78e7e7d9904e271f59c939a3e02 + languageName: node + linkType: hard + "@opentelemetry/context-zone-peer-dep@npm:1.25.1": version: 1.25.1 resolution: "@opentelemetry/context-zone-peer-dep@npm:1.25.1" @@ -4807,7 +4825,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/core@npm:^1.26.0": +"@opentelemetry/core@npm:1.26.0, @opentelemetry/core@npm:^1.26.0": version: 1.26.0 resolution: "@opentelemetry/core@npm:1.26.0" dependencies: @@ -4848,6 +4866,20 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/instrumentation-http@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.53.0" + dependencies: + "@opentelemetry/core": 1.26.0 + "@opentelemetry/instrumentation": 0.53.0 + "@opentelemetry/semantic-conventions": 1.27.0 + semver: ^7.5.2 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 4ee569f7fc8c7ce50fabaff016d33577f36e63272b0634ac45806d70bffdf38fcf09db3cd9dd27c3150f6c4547fec673c356c419a6ed2399ff2849b9487a6e89 + languageName: node + linkType: hard + "@opentelemetry/instrumentation@npm:0.52.1": version: 0.52.1 resolution: "@opentelemetry/instrumentation@npm:0.52.1" @@ -4864,6 +4896,22 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/instrumentation@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": 0.53.0 + "@types/shimmer": ^1.2.0 + import-in-the-middle: ^1.8.1 + require-in-the-middle: ^7.1.1 + semver: ^7.5.2 + shimmer: ^1.2.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: a386fe066eab71129a6edbc883ab407b1022850e8acc4750029a12e8730588a8b81442d0b008aaddb46f7614af40d19d331e7348790ca2d08ba8eed6d23ffdae + languageName: node + linkType: hard + "@opentelemetry/otlp-exporter-base@npm:0.52.1": version: 0.52.1 resolution: "@opentelemetry/otlp-exporter-base@npm:0.52.1" @@ -4893,6 +4941,28 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/propagator-b3@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/propagator-b3@npm:1.26.0" + dependencies: + "@opentelemetry/core": 1.26.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: c2e99a8ed2814cf5b8e6e2a79411f2f6d668b7d5fc8351e5302ea4149601a96ec655422cf59470c66d8a408850f8a6b5156bf7deac7afb07d3f7a935c51fff04 + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/propagator-jaeger@npm:1.26.0" + dependencies: + "@opentelemetry/core": 1.26.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: a0ac3888c86f1b4671c7ca520396b89b4c47fa9e9d976bd014472d2b7786e7c5bdf4823a6e2a900fed5ea5dfe23eda0bdf6740e77c1352f2c0f82b13a71c03df + languageName: node + linkType: hard + "@opentelemetry/resources@npm:1.25.1": version: 1.25.1 resolution: "@opentelemetry/resources@npm:1.25.1" @@ -4905,6 +4975,18 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/resources@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/resources@npm:1.26.0" + dependencies: + "@opentelemetry/core": 1.26.0 + "@opentelemetry/semantic-conventions": 1.27.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: f70b0fdf4fb00c950bc30084818c92a5339f1be5d709bd681ab14453e877d6bb9f700324b8e65a0eabfeea618d01ed071abf9088e00fa0bf7f3305b1abad22cb + languageName: node + linkType: hard + "@opentelemetry/sdk-logs@npm:0.52.1": version: 0.52.1 resolution: "@opentelemetry/sdk-logs@npm:0.52.1" @@ -4944,6 +5026,35 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/sdk-trace-base@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/sdk-trace-base@npm:1.26.0" + dependencies: + "@opentelemetry/core": 1.26.0 + "@opentelemetry/resources": 1.26.0 + "@opentelemetry/semantic-conventions": 1.27.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: a4f4ddf644fd0d79b2bd49e4377143688d2aa657643a470d8bed6696f26817598fb4e9f16ba2d8c237292af56f06eec56594a7b4cc417d4ea7e490a45a22113b + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-node@npm:^1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/sdk-trace-node@npm:1.26.0" + dependencies: + "@opentelemetry/context-async-hooks": 1.26.0 + "@opentelemetry/core": 1.26.0 + "@opentelemetry/propagator-b3": 1.26.0 + "@opentelemetry/propagator-jaeger": 1.26.0 + "@opentelemetry/sdk-trace-base": 1.26.0 + semver: ^7.5.2 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 1d63bed8fc36496698919ccd25be3b7b0e0d0bf9478f413a26bdbfe0bf0d4166bf58bbbee2415fb2fe42d3008b5c32ec7e4e42f2cb6d18b665b349eb025c15eb + languageName: node + linkType: hard + "@opentelemetry/sdk-trace-web@npm:1.25.1": version: 1.25.1 resolution: "@opentelemetry/sdk-trace-web@npm:1.25.1" @@ -4964,7 +5075,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:1.27.0": +"@opentelemetry/semantic-conventions@npm:1.27.0, @opentelemetry/semantic-conventions@npm:^1.27.0": version: 1.27.0 resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" checksum: 26d85f8d13c8c64024f7a84528cff41d56afc9829e7ff8a654576404f8b2c1a9c264adcc6fa5a9551bacdd938a4a464041fa9493e0a722e5605f2c2ae6752398 @@ -11023,10 +11134,10 @@ __metadata: languageName: node linkType: hard -"@types/shimmer@npm:^1.0.2": - version: 1.0.4 - resolution: "@types/shimmer@npm:1.0.4" - checksum: f1e7f8b773c34ea21b69686cb100117bd94cc0d1f043e3fc50683453b9936d1295c4f48e1872766556234a9ec48ea37fc7e6b5e56212f66ec65d5b2b5d73092b +"@types/shimmer@npm:^1.0.2, @types/shimmer@npm:^1.2.0": + version: 1.2.0 + resolution: "@types/shimmer@npm:1.2.0" + checksum: f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c languageName: node linkType: hard @@ -12570,6 +12681,9 @@ __metadata: version: 0.0.0-use.local resolution: "appsmith-rts@workspace:packages/rts" dependencies: + "@opentelemetry/instrumentation-http": ^0.53.0 + "@opentelemetry/sdk-trace-node": ^1.26.0 + "@opentelemetry/semantic-conventions": ^1.27.0 "@shared/ast": "workspace:^" "@types/express": ^4.17.14 "@types/jest": ^29.2.3