Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { usePluginActionContext } from "../../../../PluginActionContext";
import { QUERY_EDITOR_FORM_NAME } from "ee/constants/forms";
import { reduxForm } from "redux-form";
import { Flex } from "@appsmith/ads";
import { PluginPackageName } from "entities/Plugin";
import AppsmithAIDeprecationCallout from "components/editorComponents/AppsmithAIDeprecationCallout";
import { useGoogleSheetsSetDefaultProperty } from "./hooks/useGoogleSheetsSetDefaultProperty";
import { useFormData } from "./hooks/useFormData";

const UQIEditorForm = () => {
const {
editorConfig,
plugin: { uiComponent },
} = usePluginActionContext();
const { editorConfig, plugin } = usePluginActionContext();
const { uiComponent } = plugin;

// Set default values for Google Sheets
useGoogleSheetsSetDefaultProperty();
Expand All @@ -29,6 +29,9 @@ const UQIEditorForm = () => {
flexDirection="column"
w="100%"
>
{plugin.packageName === PluginPackageName.APPSMITH_AI && (
<AppsmithAIDeprecationCallout />
)}
<FormRender
editorConfig={editorConfig}
formData={data}
Expand Down
5 changes: 5 additions & 0 deletions app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,11 @@ export const CREATE_NEW_DATASOURCE_GRAPHQL_API = () => "GraphQL API";
export const CREATE_NEW_API_SECTION_HEADER = () => "APIs";
export const CREATE_NEW_SAAS_SECTION_HEADER = () => "SaaS Integrations";
export const CREATE_NEW_AI_SECTION_HEADER = () => "AI Integrations";
// Appsmith AI deprecation (release 2.3): update this date if the shutdown date moves
export const APPSMITH_AI_KILL_DATE = "September 30, 2026";
export const APPSMITH_AI_DEPRECATION_MESSAGE = () =>
`Appsmith AI is deprecated and will stop working on ${APPSMITH_AI_KILL_DATE}. Migrate to OpenAI, Anthropic, or Google AI with your own API key. Uploaded files (file context) will not be available after this date.`;
export const APPSMITH_AI_DEPRECATION_LEARN_MORE = () => "Learn how to migrate";
export const CONNECT_A_DATASOURCE_HEADING = () => "Connect a datasource";
export const CONNECT_A_DATASOURCE_SUBHEADING = () =>
"Select a sample datasource or connect your own";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { ThemeProvider } from "styled-components";
import { BrowserRouter as Router } from "react-router-dom";
import { lightTheme } from "selectors/themeSelectors";
import AppsmithAIDeprecationCallout from "./AppsmithAIDeprecationCallout";
import {
APPSMITH_AI_DEPRECATION_LEARN_MORE,
APPSMITH_AI_KILL_DATE,
createMessage,
} from "ee/constants/messages";

const renderCallout = () =>
render(
<ThemeProvider theme={lightTheme}>
<Router>
<AppsmithAIDeprecationCallout />
</Router>
</ThemeProvider>,
);

describe("AppsmithAIDeprecationCallout", () => {
it("renders the deprecation message with the kill date", () => {
renderCallout();

expect(
screen.getByTestId("t--appsmith-ai-deprecation-callout"),
).toBeInTheDocument();
expect(screen.getByText(/Appsmith AI is deprecated/i)).toBeInTheDocument();
expect(
screen.getByText(new RegExp(APPSMITH_AI_KILL_DATE)),
).toBeInTheDocument();
});

it("renders the migration docs link", () => {
renderCallout();

expect(
screen.getByText(createMessage(APPSMITH_AI_DEPRECATION_LEARN_MORE)),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import styled from "styled-components";
import { Callout } from "@appsmith/ads";
import { DocsLink, openDoc } from "constants/DocumentationLinks";
import {
APPSMITH_AI_DEPRECATION_LEARN_MORE,
APPSMITH_AI_DEPRECATION_MESSAGE,
createMessage,
} from "ee/constants/messages";

const CalloutWrapper = styled.div`
width: 100%;
margin-bottom: var(--ads-v2-spaces-4);
`;

/**
* Deprecation notice for the managed Appsmith AI plugin (release 2.3).
* Shown on existing Appsmith AI datasources and queries; creating new
* Appsmith AI datasources is blocked separately (client filter + server guard).
*/
function AppsmithAIDeprecationCallout() {
return (
<CalloutWrapper data-testid="t--appsmith-ai-deprecation-callout">
<Callout
kind="warning"
links={[
{
children: createMessage(APPSMITH_AI_DEPRECATION_LEARN_MORE),
onClick: () => openDoc(DocsLink.APPSMITH_AI_DEPRECATION),
},
]}
>
{createMessage(APPSMITH_AI_DEPRECATION_MESSAGE)}
</Callout>
</CalloutWrapper>
);
}

export default AppsmithAIDeprecationCallout;
4 changes: 4 additions & 0 deletions app/client/src/constants/DocumentationLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum DocsLink {
QUERY = "QUERY",
TROUBLESHOOT_ERROR = "TROUBLESHOOT_ERROR",
QUERY_SETTINGS = "QUERY_SETTINGS",
APPSMITH_AI_DEPRECATION = "APPSMITH_AI_DEPRECATION",
}

const LinkData: Record<DocsLink, string> = {
Expand All @@ -22,6 +23,9 @@ const LinkData: Record<DocsLink, string> = {
"https://docs.appsmith.com/help-and-support/troubleshooting-guide",
QUERY_SETTINGS:
"https://docs.appsmith.com/connect-data/reference/query-settings",
// TODO: replace with the dedicated Appsmith AI → BYOK migration guide once published
APPSMITH_AI_DEPRECATION:
"https://docs.appsmith.com/connect-data/reference/appsmith-ai",
Comment on lines +26 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Point the migration link at migration guidance.

The configured destination currently opens the Appsmith AI reference page, which documents connecting and querying Appsmith AI rather than the BYOK migration path promised by “Learn how to migrate.” Users will be sent to the wrong documentation from the deprecation banner. Replace it with the published migration guide, or avoid labeling this link as migration guidance until that guide exists. (docs.appsmith.com)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/client/src/constants/DocumentationLinks.ts` around lines 26 - 28, Update
the APPSMITH_AI_DEPRECATION documentation link to the published Appsmith AI BYOK
migration guide, ensuring the deprecation banner’s “Learn how to migrate”
destination provides migration guidance rather than the general Appsmith AI
reference page.

Source: MCP tools

};

export const openDoc = (type: DocsLink, link?: string, subType?: string) => {
Expand Down
6 changes: 6 additions & 0 deletions app/client/src/pages/Editor/DataSourceEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { formValuesToDatasource } from "PluginActionEditor/transformers/RestAPID
import { DSFormHeader } from "./DSFormHeader";
import type { PluginType } from "entities/Plugin";
import { DatasourceComponentTypes, PluginPackageName } from "entities/Plugin";
import AppsmithAIDeprecationCallout from "components/editorComponents/AppsmithAIDeprecationCallout";
import DSDataFilter from "ee/components/DSDataFilter";
import { DEFAULT_ENV_ID } from "ee/api/ApiUtils";
import { isStorageEnvironmentCreated } from "ee/utils/Environments";
Expand Down Expand Up @@ -1031,6 +1032,11 @@ class DatasourceEditorRouter extends React.Component<Props, State> {
showingTabsOnViewMode && "db-form-resizer-content-show-tabs"
}`}
>
{/* Mounted above DSEditorWrapper: the wrapper is a row flex, so a
child callout would render as a side column instead of a banner */}
{pluginPackageName === PluginPackageName.APPSMITH_AI && (
<AppsmithAIDeprecationCallout />
)}
<DSEditorWrapper
className={!!isOnboardingFlow ? "onboarding-flow" : ""}
isCreatingAiAgent={
Expand Down
9 changes: 7 additions & 2 deletions app/client/src/pages/Editor/IntegrationEditor/AIPlugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { connect } from "react-redux";
import { createTempDatasourceFromForm } from "actions/datasourceActions";
import type { DefaultRootState } from "react-redux";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { type Plugin, PluginType } from "entities/Plugin";
import { type Plugin, PluginPackageName, PluginType } from "entities/Plugin";
import { getAssetUrl, isAirgapped } from "ee/utils/airgapHelpers";
import {
DatasourceContainer,
Expand Down Expand Up @@ -93,7 +93,12 @@ const mapStateToProps = (state: DefaultRootState) => {
// Sort the AI plugins alphabetically
return a.name.localeCompare(b.name);
})
.filter((plugin) => plugin.type === PluginType.AI),
.filter(
(plugin) =>
plugin.type === PluginType.AI &&
// Appsmith AI is deprecated — creating new datasources for it is blocked
plugin.packageName !== PluginPackageName.APPSMITH_AI,
),
searchedPlugin,
) as Plugin[];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from "react";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import store from "store";
import { render } from "test/testUtils";
import { PluginPackageName } from "entities/Plugin";
import AIPlugins from "../AIPlugins";

const aiPlugins = [
{
id: "appsmith-ai-plugin-id",
name: "Appsmith AI",
type: "AI",
packageName: PluginPackageName.APPSMITH_AI,
iconLocation: "",
},
{
id: "openai-plugin-id",
name: "OpenAI",
type: "AI",
packageName: "openai-plugin",
iconLocation: "",
},
];

describe("AIPlugins create-new list", () => {
it("hides deprecated Appsmith AI but keeps other AI plugins", () => {
store.dispatch({
type: ReduxActionTypes.FETCH_PLUGINS_SUCCESS,
payload: aiPlugins,
});

const { container } = render(
<AIPlugins pageId="pageId" showUnsupportedPluginDialog={() => {}} />,
);

// BYOK AI plugins must remain creatable
expect(
container.querySelector(".t--createBlankApi-openai-plugin"),
).not.toBeNull();
// The deprecated managed Appsmith AI plugin must not be offered for creation
expect(
container.querySelector(
`.t--createBlankApi-${PluginPackageName.APPSMITH_AI}`,
),
).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public interface DatasourceServiceCE {

Mono<Datasource> create(Datasource datasource);

/**
* Same as {@link #create(Datasource)} but skips the deprecated-plugin creation guard. Meant for internal flows
* (e.g. forking an application) that must keep working for datasources of deprecated plugins that already exist.
*/
Mono<Datasource> createWithoutDeprecationCheck(Datasource datasource);

Mono<Datasource> createWithoutPermissions(Datasource datasource);

Mono<Datasource> createWithoutPermissions(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.appsmith.server.datasources.base;

import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.constants.PluginConstants;
import com.appsmith.external.enums.FeatureFlagEnum;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DatasourceConfiguration;
Expand Down Expand Up @@ -139,11 +140,40 @@ public DatasourceServiceCEImpl(

@Override
public Mono<Datasource> create(Datasource datasource) {
return validatePluginNotDeprecated(datasource)
.then(Mono.defer(() -> createWithoutDeprecationCheck(datasource)));
}

@Override
public Mono<Datasource> createWithoutDeprecationCheck(Datasource datasource) {
return workspacePermission
.getDatasourceCreatePermission()
.flatMap(permission -> createEx(datasource, permission, false, null));
}

/**
* Plugins for which creating new datasources is blocked because the plugin is deprecated.
* EE overrides this to extend the blocked list (e.g. appsmith-agent-plugin).
*/
protected Set<String> getDeprecatedPluginPackageNames() {
return Set.of(PluginConstants.PackageName.APPSMITH_AI_PLUGIN);
}

private Mono<Void> validatePluginNotDeprecated(Datasource datasource) {
// Only block brand-new datasources. Calls that carry an id are storage-saves for datasources that already
// exist and must keep working so users can keep using them until they migrate. A missing pluginId falls
// through to the INVALID_PARAMETER validation in createEx.
if (hasText(datasource.getId()) || !hasText(datasource.getPluginId())) {
return Mono.empty();
}

return pluginService
.findById(datasource.getPluginId())
.filter(plugin -> getDeprecatedPluginPackageNames().contains(plugin.getPackageName()))
.flatMap(plugin -> Mono.<Void>error(
new AppsmithException(AppsmithError.DEPRECATED_DATASOURCE_PLUGIN, plugin.getName())));
}

// TODO: Check usage
@Override
public Mono<Datasource> createWithoutPermissions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,17 @@ private Mono<Datasource> createSuffixedDatasource(Datasource datasource) {
private Mono<Datasource> createSuffixedDatasource(Datasource datasource, String name, int suffix) {
final String actualName = name + (suffix == 0 ? "" : " (" + suffix + ")");
datasource.setName(actualName);
return datasourceService.create(datasource).onErrorResume(DuplicateKeyException.class, error -> {
if (error.getMessage() != null
&& error.getMessage().contains("workspace_datasource_deleted_compound_index")) {
// The duplicate key error is because of the `name` field.
return createSuffixedDatasource(datasource, name, 1 + suffix);
}
throw error;
});
// Forking must keep working for apps that already contain datasources of deprecated plugins,
// so skip the deprecated-plugin creation guard here.
return datasourceService
.createWithoutDeprecationCheck(datasource)
.onErrorResume(DuplicateKeyException.class, error -> {
if (error.getMessage() != null
&& error.getMessage().contains("workspace_datasource_deleted_compound_index")) {
// The duplicate key error is because of the `name` field.
return createSuffixedDatasource(datasource, name, 1 + suffix);
}
throw error;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,15 @@ public enum AppsmithError {
"Datasource cannot be deleted",
ErrorType.BAD_REQUEST,
null),
DEPRECATED_DATASOURCE_PLUGIN(
400,
AppsmithErrorCode.DEPRECATED_DATASOURCE_PLUGIN.getCode(),
"Creating new datasources with the {0} plugin is not supported because the plugin is deprecated."
+ " Please create an OpenAI, Anthropic, or Google AI datasource with your own API key instead.",
AppsmithErrorAction.DEFAULT,
"Plugin deprecated",
ErrorType.BAD_REQUEST,
null),
WORKSPACE_ID_NOT_GIVEN(
400,
AppsmithErrorCode.WORKSPACE_ID_NOT_GIVEN.getCode(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public enum AppsmithErrorCode {
INVALID_DATASOURCE("AE-DTS-4013", "Invalid datasource"),
INVALID_DATASOURCE_CONFIGURATION("AE-DTS-4015", "Invalid datasource configuration"),
DATASOURCE_HAS_ACTIONS("AE-DTS-4030", "Datasource has actions"),
DEPRECATED_DATASOURCE_PLUGIN("AE-DTS-4031", "Datasource creation is blocked for a deprecated plugin"),
APPLICATION_FORKING_NOT_ALLOWED("AE-FRK-4034", "Application forking not allowed"),
INVALID_GIT_CONFIGURATION("AE-GIT-4031", "Invalid git configuration"),
INVALID_GIT_SSH_CONFIGURATION("AE-GIT-4032", "Invalid git ssh configuration"),
Expand Down
Loading
Loading