diff --git a/app/client/packages/design-system/headless/src/components/TextArea/src/TextArea.tsx b/app/client/packages/design-system/headless/src/components/TextArea/src/TextArea.tsx index b9f0e63da03..d787d6e408e 100644 --- a/app/client/packages/design-system/headless/src/components/TextArea/src/TextArea.tsx +++ b/app/client/packages/design-system/headless/src/components/TextArea/src/TextArea.tsx @@ -48,9 +48,17 @@ function TextArea(props: TextAreaProps, ref: TextAreaRef) { } input.style.alignSelf = "start"; input.style.height = "auto"; - // offsetHeight - clientHeight accounts for the border/padding. + + const computedStyle = getComputedStyle(input); + const paddingTop = parseFloat(computedStyle.paddingTop); + const paddingBottom = parseFloat(computedStyle.paddingBottom); input.style.height = `${ - input.scrollHeight + (input.offsetHeight - input.clientHeight) + // subtract comptued padding and border to get the actual content height + input.scrollHeight - + paddingTop - + paddingBottom + + // Also, adding 1px to fix a bug in browser where there is a scrolllbar on certain heights + 1 }px`; input.style.overflow = prevOverflow; input.style.alignSelf = prevAlignment; diff --git a/app/client/src/ce/api/ApplicationApi.tsx b/app/client/src/ce/api/ApplicationApi.tsx index dec3fcd7a53..2ffa8cc883f 100644 --- a/app/client/src/ce/api/ApplicationApi.tsx +++ b/app/client/src/ce/api/ApplicationApi.tsx @@ -204,6 +204,7 @@ export interface UpdateApplicationResponse { isPublic: boolean; pages: PageDefaultMeta[]; appIsExample: boolean; + unreadCommentThreads: number; color: string; icon: IconNames; slug: string; diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx index b6868a72416..8101d54b292 100644 --- a/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx +++ b/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx @@ -1,3 +1,5 @@ +import type { IPanelProps } from "@blueprintjs/core"; +import equal from "fast-deep-equal/es6"; import type { ReactElement } from "react"; import React, { useCallback, @@ -6,47 +8,44 @@ import React, { useMemo, useRef, } from "react"; -import equal from "fast-deep-equal/es6"; import { useDispatch, useSelector } from "react-redux"; import { getWidgetPropsForPropertyPane } from "selectors/propertyPaneSelectors"; -import type { IPanelProps } from "@blueprintjs/core"; -import PropertyPaneTitle from "./PropertyPaneTitle"; -import PropertyControlsGenerator from "./PropertyControlsGenerator"; -import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; +import { + BINDING_WIDGET_WALKTHROUGH_DESC, + BINDING_WIDGET_WALKTHROUGH_TITLE, + createMessage, +} from "@appsmith/constants/messages"; +import { AB_TESTING_EVENT_KEYS } from "@appsmith/entities/FeatureFlag"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; +import WidgetFactory from "WidgetProvider/factory"; import { copyWidget, deleteSelectedWidget } from "actions/widgetActions"; -import ConnectDataCTA, { actionsExist } from "./ConnectDataCTA"; -import PropertyPaneConnections from "./PropertyPaneConnections"; +import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; +import { PROPERTY_PANE_ID } from "components/editorComponents/PropertyPaneSidebar"; +import WalkthroughContext from "components/featureWalkthrough/walkthroughContext"; +import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { WIDGET_ID_SHOW_WALKTHROUGH } from "constants/WidgetConstants"; +import { Button } from "design-system"; +import { SelectionRequestType } from "sagas/WidgetSelectUtils"; +import { getWidgets } from "sagas/selectors"; +import { getCurrentUser } from "selectors/usersSelectors"; import type { InteractionAnalyticsEventDetail } from "utils/AppsmithUtils"; import { INTERACTION_ANALYTICS_EVENT } from "utils/AppsmithUtils"; -import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; -import { buildDeprecationWidgetMessage, isWidgetDeprecated } from "../utils"; -import { Button, Callout } from "design-system"; -import WidgetFactory from "WidgetProvider/factory"; -import { PropertyPaneTab } from "./PropertyPaneTab"; -import { renderWidgetCallouts, 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 { useWidgetSelection } from "utils/hooks/useWidgetSelection"; import localStorage from "utils/localStorage"; -import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants"; -import { PROPERTY_PANE_ID } from "components/editorComponents/PropertyPaneSidebar"; import { isUserSignedUpFlagSet, setFeatureWalkthroughShown, } from "utils/storage"; -import { - BINDING_WIDGET_WALKTHROUGH_DESC, - BINDING_WIDGET_WALKTHROUGH_TITLE, - createMessage, -} from "@appsmith/constants/messages"; -import { getWidgets } from "sagas/selectors"; -import { getCurrentUser } from "selectors/usersSelectors"; -import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; -import { SelectionRequestType } from "sagas/WidgetSelectUtils"; +import ConnectDataCTA, { actionsExist } from "./ConnectDataCTA"; +import PropertyControlsGenerator from "./PropertyControlsGenerator"; +import PropertyPaneConnections from "./PropertyPaneConnections"; +import { PropertyPaneSearchInput } from "./PropertyPaneSearchInput"; +import { PropertyPaneTab } from "./PropertyPaneTab"; +import PropertyPaneTitle from "./PropertyPaneTitle"; +import { renderWidgetCallouts, useSearchText } from "./helpers"; +import { sendPropertyPaneSearchAnalytics } from "./propertyPaneSearch"; // 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 @@ -242,13 +241,6 @@ function PropertyPaneView( if (!widgetProperties) return null; - // Building Deprecation Messages - const { isDeprecated, widgetReplacedWith } = isWidgetDeprecated( - widgetProperties.type, - ); - // generate messages - const deprecationMessage = buildDeprecationWidgetMessage(widgetReplacedWith); - const isContentConfigAvailable = WidgetFactory.getWidgetPropertyPaneContentConfig( widgetProperties.type, @@ -285,11 +277,7 @@ function PropertyPaneView( widgetType={widgetProperties?.type} /> )} - {isDeprecated && ( - - {deprecationMessage} - - )} + {renderWidgetCallouts(widgetProperties)} diff --git a/app/client/src/pages/Editor/PropertyPane/helpers.tsx b/app/client/src/pages/Editor/PropertyPane/helpers.tsx index 3cf9a5fe572..0845ad8a8df 100644 --- a/app/client/src/pages/Editor/PropertyPane/helpers.tsx +++ b/app/client/src/pages/Editor/PropertyPane/helpers.tsx @@ -1,17 +1,16 @@ +import type { WidgetCallout } from "WidgetProvider/constants"; +import WidgetFactory from "WidgetProvider/factory"; import type { PropertyPaneConfig, PropertyPaneControlConfig, PropertyPaneSectionConfig, } from "constants/PropertyControlConstants"; +import { Callout } from "design-system"; import { debounce } from "lodash"; -import { useCallback, useState } from "react"; +import React, { useCallback, useState } from "react"; import { layoutSystemBasedPropertyFilter } from "sagas/WidgetEnhancementHelpers"; -import type { WidgetProps } from "widgets/BaseWidget"; -import { Callout } from "design-system"; -import React from "react"; -import WidgetFactory from "WidgetProvider/factory"; -import type { WidgetCallout } from "WidgetProvider/constants"; import { isDynamicValue } from "utils/DynamicBindingUtils"; +import type { WidgetProps } from "widgets/BaseWidget"; export function useSearchText(initialVal: string) { const [searchText, setSearchText] = useState(initialVal); @@ -104,6 +103,7 @@ export function updateConfigPaths( export function renderWidgetCallouts(props: WidgetProps): JSX.Element[] { const { getEditorCallouts } = WidgetFactory.getWidgetMethods(props.type); + if (getEditorCallouts) { const callouts: WidgetCallout[] = getEditorCallouts(props); return callouts.map((callout, index) => { diff --git a/app/client/src/utils/autocomplete/CodemirrorTernService.ts b/app/client/src/utils/autocomplete/CodemirrorTernService.ts index 19059ebec2e..63b8b037a48 100644 --- a/app/client/src/utils/autocomplete/CodemirrorTernService.ts +++ b/app/client/src/utils/autocomplete/CodemirrorTernService.ts @@ -112,6 +112,25 @@ export function isCustomKeywordType( ); } +// Define the regex for extracting the final object path +const FINAL_OBJECT_PATH_REGEX = /(?:\w+\.)*\w+$/; + +/** + * Extracts the final object path from a given input string. + * The final object path is the rightmost dot-separated path in the string. + * + * @param {string} input - The input string from which to extract the object path. + * @returns {string|null} - The extracted object path or null if no match is found. + * + * Example: + * Input: '\tconst k = PageQuery.run' + * Output: 'PageQuery.run' + */ +export function extractFinalObjectPath(input: string) { + const match = (input || "")?.trim().match(FINAL_OBJECT_PATH_REGEX); + return match ? match[0] : null; +} + export function getDataType(type: string): AutocompleteDataType { if (type === "?") return AutocompleteDataType.UNKNOWN; else if (type === "number") return AutocompleteDataType.NUMBER; @@ -177,12 +196,14 @@ class CodeMirrorTernService { string, DataTreeDefEntityInformation >(); + entityDef: Def; options: { async: boolean }; recentEntities: string[] = []; constructor(options: { async: boolean }) { this.options = options; this.server = new TernWorkerServer(this); + this.entityDef = {}; } resetServer = () => { @@ -397,6 +418,7 @@ class CodeMirrorTernService { this.server.deleteDefs(name); } + this.entityDef = def || {}; if (entityInfo) this.defEntityInformation = entityInfo; } @@ -455,9 +477,21 @@ class CodeMirrorTernService { completion.origin === "DATA_TREE" && this.defEntityInformation.has(completion.name); let completionText = completion.name + after; + const completedLine = lineValue.substring(0, from.ch) + completion.name; + const entityPath = extractFinalObjectPath(completedLine); + if (dataType === "FUNCTION" && !completion.origin?.startsWith("LIB/")) { if (token.type !== "string" && token.string !== "[") { - completionText = completionText + "()"; + const entityDef = entityPath && this.entityDef[entityPath]; + if ( + entityDef && + typeof entityDef === "object" && + "!fnParams" in entityDef + ) { + completionText = completionText + `(${entityDef["!fnParams"]})`; + } else { + completionText = completionText + "()"; + } } } const codeMirrorCompletion: Completion = { @@ -1204,7 +1238,10 @@ export default new CodeMirrorTernService({ function dotToBracketNotationAtToken(token: CodeMirror.Token) { return (cm: CodeMirror.Editor, hints: Hints, curr: Hint) => { let completion = curr.text; - if (token.type === "string") { + if ( + token.type === "string" || + ("type" in curr && curr.type === AutocompleteDataType.FUNCTION) + ) { // | represents the cursor // Cases like JSObject1["myV|"] cm.replaceRange(completion, hints.from, hints.to); diff --git a/app/client/src/utils/autocomplete/__tests__/TernServer.test.ts b/app/client/src/utils/autocomplete/__tests__/TernServer.test.ts index 93cafbb4ee5..5f240c19737 100644 --- a/app/client/src/utils/autocomplete/__tests__/TernServer.test.ts +++ b/app/client/src/utils/autocomplete/__tests__/TernServer.test.ts @@ -4,6 +4,7 @@ import type { } from "../CodemirrorTernService"; import CodemirrorTernService, { createCompletionHeader, + extractFinalObjectPath, } from "../CodemirrorTernService"; import { AutocompleteDataType } from "../AutocompleteDataType"; import { MockCodemirrorEditor } from "../../../../test/__mocks__/CodeMirrorEditorMock"; @@ -11,6 +12,8 @@ import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; import _ from "lodash"; import { AutocompleteSorter, ScoredCompletion } from "../AutocompleteSortRules"; import type CodeMirror from "codemirror"; +import type { Def } from "tern"; +import type { Doc } from "codemirror"; jest.mock("utils/getCodeMirrorNamespace", () => { const actual = jest.requireActual("utils/getCodeMirrorNamespace"); @@ -421,3 +424,304 @@ describe("Tern server sorting", () => { expect(scoredCompletion3.score).toBe(2 ** 8 + 2 ** 4 + 2 ** 3); }); }); + +describe("Tern server completion", () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it("identifies fnParams for the current line and applies to the completion list", () => { + const entityDef: Def = { + "!name": "DATA_TREE", + QueryModule11: { + "!doc": + "Object that contains the properties required to run queries and access the query data.", + "!url": + "https://docs.appsmith.com/reference/appsmith-framework/query-object", + data: { + "!doc": + "A read-only property that contains the response body from the last successful execution of this query.", + "!url": + "https://docs.appsmith.com/reference/appsmith-framework/query-object#data-array", + "!type": "?", + }, + run: { + "!type": "fn(inputs: {gender: any, limit: any}) -> +Promise", + "!fnParams": '{ gender: "male", limit: "5" }', + "!url": + "https://docs.appsmith.com/reference/appsmith-framework/query-object#queryrun", + "!doc": "Executes the query with the given input values.", + }, + }, + "QueryModule11.data": { + "!doc": + "A read-only property that contains the response body from the last successful execution of this query.", + "!url": + "https://docs.appsmith.com/reference/appsmith-framework/query-object#data-array", + "!type": "?", + }, + "QueryModule11.run": { + "!type": "fn(inputs: {gender: any, limit: any}) -> +Promise", + "!fnParams": '{ gender: "male", limit: "5" }', + "!url": + "https://docs.appsmith.com/reference/appsmith-framework/query-object#queryrun", + "!doc": "Executes the query with the given input values.", + }, + "!define": {}, + }; + const data = { + start: { + line: 10, + ch: 22, + }, + end: { + line: 10, + ch: 30, + }, + isProperty: false, + isObjectKey: false, + completions: [ + { + name: "QueryModule11", + type: "QueryModule11", + doc: "Object that contains the properties required to run queries and access the query data.", + url: "https://docs.appsmith.com/reference/appsmith-framework/query-object", + origin: "DATA_TREE", + }, + { + name: "QueryModule11.data", + type: "?", + doc: "A read-only property that contains the response body from the last successful execution of this query.", + url: "https://docs.appsmith.com/reference/appsmith-framework/query-object#data-array", + origin: "DATA_TREE", + }, + { + name: "QueryModule11.run", + type: "fn(inputs: {gender: ?, limit: ?}) -> Promise", + doc: "Executes the query with the given input values.", + url: "https://docs.appsmith.com/reference/appsmith-framework/query-object#queryrun", + origin: "DATA_TREE", + }, + ], + }; + + const expectedValue = [ + { + text: "QueryModule11.data", + displayText: "QueryModule11.data", + className: + "CodeMirror-Tern-completion CodeMirror-Tern-completion-unknown", + data: { + name: "QueryModule11.data", + type: "?", + doc: "A read-only property that contains the response body from the last successful execution of this query.", + url: "https://docs.appsmith.com/reference/appsmith-framework/query-object#data-array", + origin: "DATA_TREE", + }, + origin: "DATA_TREE", + type: "UNKNOWN", + isHeader: false, + recencyWeight: 0, + isEntityName: false, + }, + { + text: "QueryModule11", + displayText: "QueryModule11", + className: + "CodeMirror-Tern-completion CodeMirror-Tern-completion-object", + data: { + name: "QueryModule11", + type: "QueryModule11", + doc: "Object that contains the properties required to run queries and access the query data.", + url: "https://docs.appsmith.com/reference/appsmith-framework/query-object", + origin: "DATA_TREE", + }, + origin: "DATA_TREE", + type: "OBJECT", + isHeader: false, + recencyWeight: 0, + isEntityName: true, + }, + { + text: 'QueryModule11.run({ gender: "male", limit: "5" })', + displayText: "QueryModule11.run", + className: "CodeMirror-Tern-completion CodeMirror-Tern-completion-fn", + data: { + name: "QueryModule11.run", + type: "fn(inputs: {gender: ?, limit: ?}) -> Promise", + doc: "Executes the query with the given input values.", + url: "https://docs.appsmith.com/reference/appsmith-framework/query-object#queryrun", + origin: "DATA_TREE", + }, + origin: "DATA_TREE", + type: "FUNCTION", + isHeader: false, + recencyWeight: 0, + isEntityName: false, + }, + ]; + + // The current cursor location that is being written in the code mirror editor + MockCodemirrorEditor.getCursor.mockResolvedValue({ + line: 10, + ch: 30, + sticky: null, + }); + MockCodemirrorEditor.getTokenAt.mockResolvedValue({ + start: 22, + end: 30, + string: "QueryMod", + type: "variable", + state: { + lastType: "variable", + cc: [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + lexical: { + indented: 4, + column: 4, + type: "vardef", + prev: { + indented: 2, + column: 18, + type: "}", + prev: { + indented: 0, + column: 15, + type: "}", + prev: { + indented: 0, + column: 0, + type: "stat", + prev: { + indented: -2, + column: 0, + type: "block", + align: false, + }, + align: true, + }, + info: null, + align: false, + }, + align: false, + }, + info: "const", + align: true, + }, + localVars: { + name: "users", + next: null, + }, + context: { + prev: { + block: false, + }, + vars: { + name: "this", + next: { + name: "arguments", + next: null, + }, + }, + block: true, + }, + indented: 4, + }, + }); + + CodemirrorTernService.entityDef = entityDef; + + // The current line that is being written in the code mirror editor + jest + .spyOn(CodemirrorTernService, "lineValue") + .mockReturnValue("\t\tconst users = await QueryMod"); + jest + .spyOn(CodemirrorTernService, "getFocusedDocValueAndPos") + .mockReturnValue({ + extraChars: 0, + value: "", + end: { + line: 10, + ch: 30, + }, + }); + jest.spyOn(CodemirrorTernService, "findDoc").mockReturnValue({ + doc: {} as Doc, + name: "", + changed: null, + }); + jest + .spyOn(AutocompleteSorter, "sort") + .mockImplementation((completions) => completions); + + CodemirrorTernService.defEntityInformation = new Map([ + [ + "QueryModule11", + { + type: "MODULE_INSTANCE", + subType: "QUERY_MODULE", + }, + ], + ]); + + const result = CodemirrorTernService.requestCallback( + null, + data, + MockCodemirrorEditor as unknown as CodeMirror.Editor, + jest.fn, + )!; + + const expectedContainingItems = _.sortBy(expectedValue, "text").map( + (item) => expect.objectContaining(item), + ); + expect(_.sortBy(result.list, "text")).toEqual(expectedContainingItems); + }); +}); + +describe("extractFinalObjectPath", () => { + it("should extract the last dot-separated path from a string", () => { + expect(extractFinalObjectPath("user.profile.name")).toEqual( + "user.profile.name", + ); + expect(extractFinalObjectPath("app.data")).toEqual("app.data"); + }); + + it("should return the last path in a code line", () => { + expect(extractFinalObjectPath("const users = GetUsers.run")).toEqual( + "GetUsers.run", + ); + }); + + it("should return the input if there are no dots", () => { + expect(extractFinalObjectPath("username")).toEqual("username"); + }); + + it("should return null for empty or whitespace-only strings", () => { + expect(extractFinalObjectPath("")).toBeNull(); + expect(extractFinalObjectPath(" ")).toBeNull(); + }); + + it("should handle strings with leading and trailing whitespace", () => { + expect(extractFinalObjectPath(" user.profile.name ")).toEqual( + "user.profile.name", + ); + }); + + it("should return null if no valid path is found", () => { + expect(extractFinalObjectPath("This is a valid code string path")).toEqual( + "path", + ); + }); +}); diff --git a/app/client/src/widgets/CircularProgressWidget/widget/index.tsx b/app/client/src/widgets/CircularProgressWidget/widget/index.tsx index 6bf9bea1ddf..18d90aef54b 100644 --- a/app/client/src/widgets/CircularProgressWidget/widget/index.tsx +++ b/app/client/src/widgets/CircularProgressWidget/widget/index.tsx @@ -1,15 +1,20 @@ import * as React from "react"; +import type { + AutocompletionDefinitions, + WidgetCallout, +} from "WidgetProvider/constants"; +import { Colors } from "constants/Colors"; +import { WIDGET_TAGS } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; +import type { Stylesheet } from "entities/AppTheming"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; +import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; import BaseWidget from "widgets/BaseWidget"; -import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; +import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { CircularProgressComponentProps } from "../component"; import CircularProgressComponent from "../component"; -import type { Stylesheet } from "entities/AppTheming"; -import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; -import type { AutocompletionDefinitions } from "WidgetProvider/constants"; -import { Colors } from "constants/Colors"; import IconSVG from "../icon.svg"; interface CircularProgressWidgetProps @@ -31,6 +36,7 @@ class CircularProgressWidget extends BaseWidget< isDeprecated: true, replacement: "PROGRESS_WIDGET", iconSVG: IconSVG, + tags: [WIDGET_TAGS.CONTENT], }; } @@ -141,6 +147,20 @@ class CircularProgressWidget extends BaseWidget< }; } + static getMethods() { + return { + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + CircularProgressWidget.getConfig().name, + ), + }, + ]; + }, + }; + } + getWidgetView() { return ( { isDeprecated: true, replacement: "DATE_PICKER_WIDGET2", needsMeta: true, + tags: [WIDGET_TAGS.INPUTS], }; } @@ -397,6 +403,20 @@ class DatePickerWidget extends BaseWidget { }; } + static getMethods() { + return { + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + DatePickerWidget.getConfig().name, + ), + }, + ]; + }, + }; + } + componentDidUpdate(prevProps: DatePickerWidgetProps) { if (this.props.dateFormat !== prevProps.dateFormat) { if (this.props.defaultDate) { diff --git a/app/client/src/widgets/DropdownWidget/widget/index.tsx b/app/client/src/widgets/DropdownWidget/widget/index.tsx index 87aa2094c0b..2a4ea93cd23 100644 --- a/app/client/src/widgets/DropdownWidget/widget/index.tsx +++ b/app/client/src/widgets/DropdownWidget/widget/index.tsx @@ -1,29 +1,31 @@ -import React from "react"; -import type { WidgetProps, WidgetState } from "../../BaseWidget"; -import BaseWidget from "../../BaseWidget"; +import { Alignment } from "@blueprintjs/core"; +import type { + AutocompletionDefinitions, + PropertyUpdates, + SnipingModeProperty, + WidgetCallout, +} from "WidgetProvider/constants"; +import { MinimumPopupWidthInPercentage } from "WidgetProvider/constants"; +import { LabelPosition } from "components/constants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import DropDownComponent from "../component"; -import _ from "lodash"; -import type { DropdownOption } from "../constants"; +import { WIDGET_TAGS, layoutConfigurations } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; +import type { Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; +import _ from "lodash"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; +import React from "react"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; -import { MinimumPopupWidthInPercentage } from "WidgetProvider/constants"; -import { LabelPosition } from "components/constants"; -import { Alignment } from "@blueprintjs/core"; -import type { Stylesheet } from "entities/AppTheming"; import { DefaultAutocompleteDefinitions, isCompactMode, } from "widgets/WidgetUtils"; -import type { - AutocompletionDefinitions, - PropertyUpdates, - SnipingModeProperty, -} from "WidgetProvider/constants"; +import type { WidgetProps, WidgetState } from "../../BaseWidget"; +import BaseWidget from "../../BaseWidget"; +import DropDownComponent from "../component"; +import type { DropdownOption } from "../constants"; import IconSVG from "../icon.svg"; -import { layoutConfigurations } from "constants/WidgetConstants"; function defaultOptionValueValidation(value: unknown): ValidationResponse { if (typeof value === "string") return { isValid: true, parsed: value.trim() }; @@ -52,6 +54,7 @@ class DropdownWidget extends BaseWidget { hideCard: true, isDeprecated: true, replacement: "SELECT_WIDGET", + tags: [WIDGET_TAGS.SELECT], }; } @@ -94,6 +97,15 @@ class DropdownWidget extends BaseWidget { }, ]; }, + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + DropdownWidget.getConfig().name, + ), + }, + ]; + }, }; } diff --git a/app/client/src/widgets/FilepickerWidget/widget/index.tsx b/app/client/src/widgets/FilepickerWidget/widget/index.tsx index e5374342ead..0788efb9eb2 100644 --- a/app/client/src/widgets/FilepickerWidget/widget/index.tsx +++ b/app/client/src/widgets/FilepickerWidget/widget/index.tsx @@ -1,26 +1,29 @@ -import React from "react"; import type { Uppy } from "@uppy/core"; -import type { WidgetProps, WidgetState } from "../../BaseWidget"; -import BaseWidget from "../../BaseWidget"; -import FilePickerComponent from "../component"; -import { ValidationTypes } from "constants/WidgetValidation"; -import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import type { DerivedPropertiesMap } from "WidgetProvider/factory"; import type Dashboard from "@uppy/dashboard"; -import shallowequal from "shallowequal"; -import _ from "lodash"; -import FileDataTypes from "./FileDataTypes"; -import log from "loglevel"; -import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions, PropertyUpdates, SnipingModeProperty, + WidgetCallout, } from "WidgetProvider/constants"; -import { importUppy, isUppyLoaded } from "utils/importUppy"; +import type { DerivedPropertiesMap } from "WidgetProvider/factory"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import { WIDGET_TAGS } from "constants/WidgetConstants"; +import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig } from "entities/AppTheming"; +import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; +import _ from "lodash"; +import log from "loglevel"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; +import React from "react"; +import shallowequal from "shallowequal"; +import { importUppy, isUppyLoaded } from "utils/importUppy"; +import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; +import type { WidgetProps, WidgetState } from "../../BaseWidget"; +import BaseWidget from "../../BaseWidget"; +import FilePickerComponent from "../component"; import IconSVG from "../icon.svg"; +import FileDataTypes from "./FileDataTypes"; class FilePickerWidget extends BaseWidget< FilePickerWidgetProps, @@ -44,6 +47,7 @@ class FilePickerWidget extends BaseWidget< hideCard: true, isDeprecated: true, replacement: "FILE_PICKER_WIDGET_V2", + tags: [WIDGET_TAGS.INPUTS], }; } @@ -80,6 +84,15 @@ class FilePickerWidget extends BaseWidget< }, ]; }, + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + FilePickerWidget.getConfig().name, + ), + }, + ]; + }, }; } diff --git a/app/client/src/widgets/FormButtonWidget/widget/index.tsx b/app/client/src/widgets/FormButtonWidget/widget/index.tsx index bc2dd961e36..568310a7306 100644 --- a/app/client/src/widgets/FormButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/FormButtonWidget/widget/index.tsx @@ -1,11 +1,11 @@ -import React from "react"; -import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; -import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; -import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import type { ButtonType } from "widgets/ButtonWidget/component"; -import ButtonComponent from "widgets/ButtonWidget/component"; -import { ValidationTypes } from "constants/WidgetValidation"; -import ButtonWidget from "widgets/ButtonWidget"; +import { Alignment } from "@blueprintjs/core"; +import type { IconName } from "@blueprintjs/icons"; +import type { + AutocompletionDefinitions, + PropertyUpdates, + SnipingModeProperty, + WidgetCallout, +} from "WidgetProvider/constants"; import type { ButtonBorderRadius, ButtonVariant, @@ -16,16 +16,19 @@ import { ButtonVariantTypes, RecaptchaTypes, } from "components/constants"; -import type { IconName } from "@blueprintjs/icons"; -import { Alignment } from "@blueprintjs/core"; -import type { ButtonWidgetProps } from "widgets/ButtonWidget/widget"; +import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import { WIDGET_TAGS } from "constants/WidgetConstants"; +import { ValidationTypes } from "constants/WidgetValidation"; import type { Stylesheet } from "entities/AppTheming"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; +import React from "react"; +import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; +import ButtonWidget from "widgets/ButtonWidget"; +import type { ButtonType } from "widgets/ButtonWidget/component"; +import ButtonComponent from "widgets/ButtonWidget/component"; +import type { ButtonWidgetProps } from "widgets/ButtonWidget/widget"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; -import type { - AutocompletionDefinitions, - PropertyUpdates, - SnipingModeProperty, -} from "WidgetProvider/constants"; import IconSVG from "../icon.svg"; class FormButtonWidget extends ButtonWidget { @@ -43,6 +46,7 @@ class FormButtonWidget extends ButtonWidget { isDeprecated: true, replacement: "BUTTON_WIDGET", needsMeta: true, + tags: [WIDGET_TAGS.BUTTONS], } as any; // TODO (Sangeeth): Type error } @@ -72,6 +76,15 @@ class FormButtonWidget extends ButtonWidget { }, ]; }, + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + FormButtonWidget.getConfig().name, + ), + }, + ]; + }, }; } diff --git a/app/client/src/widgets/IconWidget/widget/index.tsx b/app/client/src/widgets/IconWidget/widget/index.tsx index 14ff845f355..dc8d9a27b3e 100644 --- a/app/client/src/widgets/IconWidget/widget/index.tsx +++ b/app/client/src/widgets/IconWidget/widget/index.tsx @@ -1,13 +1,16 @@ +import type { WidgetCallout } from "WidgetProvider/constants"; +import type { DerivedPropertiesMap } from "WidgetProvider/factory"; +import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import { WIDGET_TAGS } from "constants/WidgetConstants"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; import React from "react"; +import styled from "styled-components"; import type { WidgetProps, WidgetState } from "../../BaseWidget"; import BaseWidget from "../../BaseWidget"; -import styled from "styled-components"; import type { IconType } from "../component"; import IconComponent from "../component"; -import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; -import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import IconSVG from "../icon.svg"; -import type { DerivedPropertiesMap } from "WidgetProvider/factory"; const IconWrapper = styled.div` display: flex; @@ -23,6 +26,7 @@ class IconWidget extends BaseWidget { hideCard: true, isDeprecated: true, replacement: "ICON_BUTTON_WIDGET", + tags: [WIDGET_TAGS.BUTTONS], }; } @@ -35,6 +39,18 @@ class IconWidget extends BaseWidget { }; } + static getMethods() { + return { + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage(IconWidget.getConfig().name), + }, + ]; + }, + }; + } + static getPropertyPaneConfig() { return []; } diff --git a/app/client/src/widgets/InputWidget/widget/index.tsx b/app/client/src/widgets/InputWidget/widget/index.tsx index e2c9b415c0a..2f76638d860 100644 --- a/app/client/src/widgets/InputWidget/widget/index.tsx +++ b/app/client/src/widgets/InputWidget/widget/index.tsx @@ -1,45 +1,51 @@ -import React from "react"; -import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; -import BaseWidget from "widgets/BaseWidget"; +import { + FIELD_REQUIRED_ERROR, + INPUT_DEFAULT_TEXT_MAX_CHAR_ERROR, + createMessage, +} from "@appsmith/constants/messages"; import { Alignment } from "@blueprintjs/core"; import type { IconName } from "@blueprintjs/icons"; -import type { TextSize } from "constants/WidgetConstants"; -import { GridDefaults, RenderModes } from "constants/WidgetConstants"; -import type { InputComponentProps } from "../component"; -import InputComponent from "../component"; +import type { + AutocompletionDefinitions, + PropertyUpdates, + SnipingModeProperty, + WidgetCallout, +} from "WidgetProvider/constants"; +import { COMPACT_MODE_MIN_ROWS } from "WidgetProvider/constants"; +import type { DerivedPropertiesMap } from "WidgetProvider/factory"; +import { LabelPosition } from "components/constants"; import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import type { TextSize } from "constants/WidgetConstants"; +import { + GridDefaults, + RenderModes, + WIDGET_TAGS, +} from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; +import React from "react"; +import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; +import { checkInputTypeTextByProps } from "widgets/BaseInputWidget/utils"; +import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; +import BaseWidget from "widgets/BaseWidget"; import { - createMessage, - FIELD_REQUIRED_ERROR, - INPUT_DEFAULT_TEXT_MAX_CHAR_ERROR, -} from "@appsmith/constants/messages"; -import type { DerivedPropertiesMap } from "WidgetProvider/factory"; -import type { InputType } from "../constants"; -import { InputTypes } from "../constants"; -import { COMPACT_MODE_MIN_ROWS } from "WidgetProvider/constants"; -import { ISDCodeDropdownOptions } from "../component/ISDCodeDropdown"; + DefaultAutocompleteDefinitions, + isCompactMode, +} from "widgets/WidgetUtils"; +import type { InputComponentProps } from "../component"; +import InputComponent from "../component"; import { CurrencyDropdownOptions } from "../component/CurrencyCodeDropdown"; -import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; +import { ISDCodeDropdownOptions } from "../component/ISDCodeDropdown"; import { formatCurrencyNumber, getDecimalSeparator, getLocale, } from "../component/utilities"; -import { LabelPosition } from "components/constants"; -import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { checkInputTypeTextByProps } from "widgets/BaseInputWidget/utils"; -import { - DefaultAutocompleteDefinitions, - isCompactMode, -} from "widgets/WidgetUtils"; -import type { - AutocompletionDefinitions, - PropertyUpdates, - SnipingModeProperty, -} from "WidgetProvider/constants"; +import type { InputType } from "../constants"; +import { InputTypes } from "../constants"; import IconSVG from "../icon.svg"; export function defaultValueValidation( @@ -144,6 +150,7 @@ class InputWidget extends BaseWidget { hideCard: true, isDeprecated: true, replacement: "INPUT_WIDGET_V2", + tags: [WIDGET_TAGS.SUGGESTED_WIDGETS, WIDGET_TAGS.INPUTS], }; } @@ -183,6 +190,15 @@ class InputWidget extends BaseWidget { }, ]; }, + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + InputWidget.getConfig().name, + ), + }, + ]; + }, }; } diff --git a/app/client/src/widgets/ListWidget/widget/index.tsx b/app/client/src/widgets/ListWidget/widget/index.tsx index 32bd576fe22..1dd7d4882e6 100644 --- a/app/client/src/widgets/ListWidget/widget/index.tsx +++ b/app/client/src/widgets/ListWidget/widget/index.tsx @@ -1,14 +1,35 @@ +import type { PrivateWidgets } from "@appsmith/entities/DataTree/types"; +import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; +import type { + AnvilConfig, + AutocompletionDefinitions, + PropertyUpdates, + SnipingModeProperty, + WidgetCallout, +} from "WidgetProvider/constants"; import { - Positioning, - ResponsiveBehavior, -} from "layoutSystems/common/utils/constants"; + BlueprintOperationTypes, + type DSLWidget, + type FlattenedWidgetProps, +} from "WidgetProvider/constants"; +import WidgetFactory from "WidgetProvider/factory"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { GridDefaults, RenderModes } from "constants/WidgetConstants"; +import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants"; +import { + GridDefaults, + RenderModes, + WIDGET_TAGS, +} from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; +import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import type { PrivateWidgets } from "@appsmith/entities/DataTree/types"; import equal from "fast-deep-equal/es6"; import { klona } from "klona/lite"; +import { renderAppsmithCanvas } from "layoutSystems/CanvasFactory"; +import { + Positioning, + ResponsiveBehavior, +} from "layoutSystems/common/utils/constants"; import { cloneDeep, compact, @@ -26,24 +47,19 @@ import { } from "lodash"; import log from "loglevel"; import memoizeOne from "memoize-one"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; import React from "react"; import shallowEqual from "shallowequal"; import { combineDynamicBindings, getDynamicBindings, } from "utils/DynamicBindingUtils"; -import { removeFalsyEntries } from "utils/helpers"; -import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; -import { generateTypeDef } from "utils/autocomplete/defCreatorUtils"; import type { ExtraDef } from "utils/autocomplete/defCreatorUtils"; -import WidgetFactory from "WidgetProvider/factory"; +import { generateTypeDef } from "utils/autocomplete/defCreatorUtils"; +import { removeFalsyEntries } from "utils/helpers"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; import BaseWidget from "widgets/BaseWidget"; -import { - BlueprintOperationTypes, - type FlattenedWidgetProps, - type DSLWidget, -} from "WidgetProvider/constants"; +import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import ListComponent, { ListComponentEmpty, ListComponentLoading, @@ -51,22 +67,12 @@ import ListComponent, { import ListPagination, { ServerSideListPagination, } from "../component/ListPagination"; +import IconSVG from "../icon.svg"; import derivedProperties from "./parseDerivedProperties"; import { PropertyPaneContentConfig, PropertyPaneStyleConfig, } from "./propertyConfig"; -import type { - AnvilConfig, - AutocompletionDefinitions, - PropertyUpdates, - SnipingModeProperty, -} from "WidgetProvider/constants"; -import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; -import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants"; -import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants"; -import IconSVG from "../icon.svg"; -import { renderAppsmithCanvas } from "layoutSystems/CanvasFactory"; const LIST_WIDGET_PAGINATION_HEIGHT = 36; @@ -92,6 +98,7 @@ class ListWidget extends BaseWidget, WidgetState> { hideCard: true, replacement: "LIST_WIDGET_V2", needsHeightForContent: true, + tags: [WIDGET_TAGS.DISPLAY], }; } @@ -498,6 +505,13 @@ class ListWidget extends BaseWidget, WidgetState> { }, ]; }, + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage(ListWidget.getConfig().name), + }, + ]; + }, }; } diff --git a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx index a53597d3ab9..f2bdbb216b0 100644 --- a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx @@ -7,23 +7,27 @@ import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; import BaseWidget from "widgets/BaseWidget"; import { Alignment } from "@blueprintjs/core"; +import type { + AutocompletionDefinitions, + WidgetCallout, +} from "WidgetProvider/constants"; +import { MinimumPopupWidthInPercentage } from "WidgetProvider/constants"; import { LabelPosition } from "components/constants"; import { Layers } from "constants/Layers"; +import { WIDGET_TAGS, layoutConfigurations } from "constants/WidgetConstants"; +import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; +import { ResponsiveBehavior } from "layoutSystems/common/utils/constants"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; import type { DraftValueType } from "rc-select/lib/Select"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; -import { MinimumPopupWidthInPercentage } from "WidgetProvider/constants"; -import MultiSelectComponent from "../component"; import { DefaultAutocompleteDefinitions, isCompactMode, } from "widgets/WidgetUtils"; -import type { AutocompletionDefinitions } from "WidgetProvider/constants"; -import { ResponsiveBehavior } from "layoutSystems/common/utils/constants"; +import MultiSelectComponent from "../component"; import IconSVG from "../icon.svg"; -import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants"; -import { layoutConfigurations } from "constants/WidgetConstants"; function defaultOptionValueValidation(value: unknown): ValidationResponse { let values: string[] = []; @@ -64,6 +68,7 @@ class MultiSelectWidget extends BaseWidget< hideCard: true, isDeprecated: true, replacement: "MULTI_SELECT_WIDGET_V2", + tags: [WIDGET_TAGS.SELECT], }; } @@ -93,6 +98,20 @@ class MultiSelectWidget extends BaseWidget< }; } + static getMethods() { + return { + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + MultiSelectWidget.getConfig().name, + ), + }, + ]; + }, + }; + } + static getAutocompleteDefinitions(): AutocompletionDefinitions { return { "!doc": diff --git a/app/client/src/widgets/ProgressBarWidget/widget/index.tsx b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx index 8a2578d3c09..12fd50f6601 100644 --- a/app/client/src/widgets/ProgressBarWidget/widget/index.tsx +++ b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx @@ -1,18 +1,23 @@ import React from "react"; +import type { DerivedPropertiesMap } from "WidgetProvider/factory"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; import BaseWidget from "widgets/BaseWidget"; -import type { DerivedPropertiesMap } from "WidgetProvider/factory"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import ProgressBarComponent from "../component"; -import { ValidationTypes } from "constants/WidgetValidation"; +import type { + AutocompletionDefinitions, + WidgetCallout, +} from "WidgetProvider/constants"; import { Colors } from "constants/Colors"; -import { BarType } from "../constants"; +import { WIDGET_TAGS } from "constants/WidgetConstants"; +import { ValidationTypes } from "constants/WidgetValidation"; import type { Stylesheet } from "entities/AppTheming"; -import type { AutocompletionDefinitions } from "WidgetProvider/constants"; import { ResponsiveBehavior } from "layoutSystems/common/utils/constants"; +import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; +import { BarType } from "../constants"; import IconSVG from "../icon.svg"; class ProgressBarWidget extends BaseWidget< @@ -30,6 +35,7 @@ class ProgressBarWidget extends BaseWidget< iconSVG: IconSVG, needsMeta: false, // Defines if this widget adds any meta properties isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets + tags: [WIDGET_TAGS.CONTENT], }; } @@ -48,6 +54,20 @@ class ProgressBarWidget extends BaseWidget< }; } + static getMethods() { + return { + getEditorCallouts(): WidgetCallout[] { + return [ + { + message: buildDeprecationWidgetMessage( + ProgressBarWidget.getConfig().name, + ), + }, + ]; + }, + }; + } + static getAutocompleteDefinitions(): AutocompletionDefinitions { return { "!doc": "Progress bar is a simple UI widget used to show progress", diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/DatasourceSpan.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/DatasourceSpan.java new file mode 100644 index 00000000000..0445211d1c0 --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/DatasourceSpan.java @@ -0,0 +1,9 @@ +package com.appsmith.external.constants.spans; + +import static com.appsmith.external.constants.spans.BaseSpan.APPSMITH_SPAN_PREFIX; + +public class DatasourceSpan { + public static final String FETCH_ALL_DATASOURCES_WITH_STORAGES = + APPSMITH_SPAN_PREFIX + "get_all_datasource_storage"; + public static final String FETCH_ALL_PLUGINS_IN_WORKSPACE = APPSMITH_SPAN_PREFIX + "get_all_plugins_in_workspace"; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceCEImpl.java index 91cc1e08a63..690847fd0e1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceCEImpl.java @@ -35,12 +35,14 @@ import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.EnvironmentPermission; import com.appsmith.server.solutions.WorkspacePermission; +import io.micrometer.observation.ObservationRegistry; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import reactor.core.observability.micrometer.Micrometer; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -59,6 +61,8 @@ import java.util.Set; import java.util.UUID; +import static com.appsmith.external.constants.spans.DatasourceSpan.FETCH_ALL_DATASOURCES_WITH_STORAGES; +import static com.appsmith.external.constants.spans.DatasourceSpan.FETCH_ALL_PLUGINS_IN_WORKSPACE; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties; import static com.appsmith.server.helpers.CollectionUtils.isNullOrEmpty; import static com.appsmith.server.helpers.DatasourceAnalyticsUtils.getAnalyticsProperties; @@ -87,6 +91,7 @@ public class DatasourceServiceCEImpl implements DatasourceServiceCE { private final EnvironmentPermission environmentPermission; private final RateLimitService rateLimitService; private final FeatureFlagService featureFlagService; + private final ObservationRegistry observationRegistry; // Defines blocking duration for test as well as connection created for query execution // This will block the creation of datasource connection for 5 minutes, in case of more than 3 failed connection @@ -112,7 +117,8 @@ public DatasourceServiceCEImpl( DatasourceStorageService datasourceStorageService, EnvironmentPermission environmentPermission, RateLimitService rateLimitService, - FeatureFlagService featureFlagService) { + FeatureFlagService featureFlagService, + ObservationRegistry observationRegistry) { this.workspaceService = workspaceService; this.sessionUserService = sessionUserService; @@ -130,6 +136,7 @@ public DatasourceServiceCEImpl( this.environmentPermission = environmentPermission; this.rateLimitService = rateLimitService; this.featureFlagService = featureFlagService; + this.observationRegistry = observationRegistry; } @Override @@ -766,14 +773,19 @@ public Flux getAllWithStorages(MultiValueMap params) @Override public Flux getAllByWorkspaceIdWithStorages(String workspaceId, AclPermission permission) { + Mono> pluginsMapMono = pluginService + .findAllPluginsInWorkspace(workspaceId) + .name(FETCH_ALL_PLUGINS_IN_WORKSPACE) + .tap(Micrometer.observation(observationRegistry)); - return repository + return pluginsMapMono.flatMapMany(pluginsMap -> repository .findAllByWorkspaceId(workspaceId, permission) .publishOn(Schedulers.boundedElastic()) .flatMap(datasource -> datasourceStorageService .findByDatasource(datasource) .publishOn(Schedulers.boundedElastic()) - .flatMap(datasourceStorageService::populateHintMessages) + .flatMap(datasourceStorage -> + datasourceStorageService.populateHintMessages(datasourceStorage, pluginsMap)) .map(datasourceStorageService::createDatasourceStorageDTOFromDatasourceStorage) .collectMap(DatasourceStorageDTO::getEnvironmentId) .flatMap(datasourceStorages -> { @@ -781,10 +793,12 @@ public Flux getAllByWorkspaceIdWithStorages(String workspaceId, AclP return Mono.just(datasource); })) .collectList() + .name(FETCH_ALL_DATASOURCES_WITH_STORAGES) + .tap(Micrometer.observation(observationRegistry)) .flatMapMany(datasourceList -> { markRecentlyUsed(datasourceList, 3); return Flux.fromIterable(datasourceList); - }); + })); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceImpl.java index 8af407c594c..1fc7bf66b43 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasources/base/DatasourceServiceImpl.java @@ -16,6 +16,7 @@ import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.EnvironmentPermission; import com.appsmith.server.solutions.WorkspacePermission; +import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -39,7 +40,8 @@ public DatasourceServiceImpl( DatasourceStorageService datasourceStorageService, EnvironmentPermission environmentPermission, RateLimitService rateLimitService, - FeatureFlagService featureFlagService) { + FeatureFlagService featureFlagService, + ObservationRegistry observationRegistry) { super( repository, @@ -57,6 +59,7 @@ public DatasourceServiceImpl( datasourceStorageService, environmentPermission, rateLimitService, - featureFlagService); + featureFlagService, + observationRegistry); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCE.java index 0d115dc5b9a..0f996656656 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCE.java @@ -4,6 +4,7 @@ import com.appsmith.external.models.DatasourceStorage; import com.appsmith.external.models.DatasourceStorageDTO; import com.appsmith.external.models.MustacheBindingToken; +import com.appsmith.server.domains.Plugin; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -37,6 +38,8 @@ Mono updateDatasourceStorage( Mono checkEnvironment(DatasourceStorage datasourceStorage); + Mono populateHintMessages(DatasourceStorage datasourceStorage, Map pluginsMap); + Mono populateHintMessages(DatasourceStorage datasourceStorage); Map getAnalyticsProperties(DatasourceStorage datasourceStorage); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCEImpl.java index 6cc989e34fa..751a7fcf281 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/datasourcestorages/base/DatasourceStorageServiceCEImpl.java @@ -244,7 +244,12 @@ private DatasourceStorage sanitizeDatasourceStorage(DatasourceStorage datasource @Override public Mono populateHintMessages(DatasourceStorage datasourceStorage) { + return this.populateHintMessages(datasourceStorage, null); + } + @Override + public Mono populateHintMessages( + DatasourceStorage datasourceStorage, Map pluginsMap) { if (datasourceStorage == null) { /* * - Not throwing an exception here because we do not throw an error in case of missing datasourceStorage. @@ -261,7 +266,13 @@ public Mono populateHintMessages(DatasourceStorage datasource return Mono.just(datasourceStorage); } - final Mono pluginMono = pluginService.findById(datasourceStorage.getPluginId()); + Mono pluginMono; + if (pluginsMap == null) { + pluginMono = pluginService.findById(datasourceStorage.getPluginId()); + } else { + pluginMono = Mono.justOrEmpty(pluginsMap.get(datasourceStorage.getPluginId())); + } + Mono pluginExecutorMono = pluginExecutorHelper .getPluginExecutor(pluginMono) .switchIfEmpty(Mono.error(new AppsmithException( diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java index db9405334e4..3b88521ed64 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java @@ -65,6 +65,10 @@ public class Application extends BaseDomain implements Artifact { @JsonView({Views.Public.class, Git.class}) boolean appIsExample = false; + @Transient + @JsonView(Views.Public.class) + long unreadCommentThreads; + @JsonView(Views.Internal.class) String clonedFromApplicationId; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java index 3362c322599..de74654b5ef 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java @@ -48,4 +48,6 @@ public interface PluginServiceCE extends CrudService { Flux saveAll(Iterable plugins); Flux findAllByIdsWithoutPermission(Set ids, List includeFields); + + Mono> findAllPluginsInWorkspace(String workspaceId); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java index 379b712dccc..ca2c8d50092 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java @@ -104,26 +104,13 @@ public PluginServiceCEImpl( } @Override - public Flux getInWorkspace(@NonNull String workspaceId) { - // TODO : Think about the various scenarios where this plugin api is called and then decide on permissions. - Mono workspaceMono = workspaceService.getById(workspaceId); - - return workspaceMono - .flatMapMany(workspace -> { - if (workspace.getPlugins() == null) { - log.debug( - "Null installed plugins found for workspace: {}. Return empty plugins", - workspace.getName()); - return Flux.empty(); - } - - Set pluginIds = workspace.getPlugins().stream() - .map(WorkspacePlugin::getPluginId) - .filter(Objects::nonNull) - .collect(Collectors.toUnmodifiableSet()); + public Mono> findAllPluginsInWorkspace(String workspaceId) { + return getAllPlugins(workspaceId).collectMap(Plugin::getId); + } - return repository.findAllById(pluginIds); - }) + @Override + public Flux getInWorkspace(@NonNull String workspaceId) { + return getAllPlugins(workspaceId) .flatMap(plugin -> getTemplates(plugin).doOnSuccess(plugin::setTemplates).thenReturn(plugin)); } @@ -634,6 +621,25 @@ private JsonNode loadPluginResourceGivenPluginAsJsonNode(Plugin plugin, String r }); } + private Flux getAllPlugins(String workspaceId) { + // TODO : Think about the various scenarios where this plugin api is called and then decide on permissions. + Mono workspaceMono = workspaceService.getById(workspaceId); + + return workspaceMono.flatMapMany(workspace -> { + if (workspace.getPlugins() == null) { + log.debug("Null installed plugins found for workspace: {}. Return empty plugins", workspace.getName()); + return Flux.empty(); + } + + Set pluginIds = workspace.getPlugins().stream() + .map(WorkspacePlugin::getPluginId) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableSet()); + + return repository.findAllById(pluginIds); + }); + } + @Data static class PluginTemplatesMeta { List templates;