Skip to content
Merged
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
3 changes: 2 additions & 1 deletion app/client/packages/mcp/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ APPSMITH_MCP_JS_ENABLED=0
# - Mongo audit/rollback records live in the MCP-owned `mcp_changes` collection.
# - Redis holds MCP-namespaced locks (appsmith:mcp:lock:*) and one-time
# confirmation tokens (appsmith:mcp:confirm:*).
# APPSMITH_MONGODB_URI takes precedence over APPSMITH_DB_URL if both are set.
# APPSMITH_DB_URL takes precedence over APPSMITH_MONGODB_URI if both are set
# (same order as the Java server and RTS).
APPSMITH_MONGODB_URI=
# APPSMITH_DB_URL=mongodb://localhost:27017/appsmith
APPSMITH_REDIS_URL=
4 changes: 2 additions & 2 deletions app/client/packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ it is off `mcp_…` tokens are rejected (401) and cannot be created or rotated a
| `APPSMITH_MCP_DATA_ENABLED` | **off** | Opt-in. While off, datasource/query tools are unregistered; spec authoring + reads remain. |
| `APPSMITH_MCP_JS_ENABLED` | **off** | Opt-in. While off, restricted JS-object tools are unregistered. |

Governed and destructive tools additionally require a MongoDB + Redis backend (`APPSMITH_MONGODB_URI` /
`APPSMITH_DB_URL` and `APPSMITH_REDIS_URL`); without them the server starts with read + spec-authoring tools only.
Governed and destructive tools additionally require a MongoDB + Redis backend (`APPSMITH_DB_URL` /
`APPSMITH_MONGODB_URI` and `APPSMITH_REDIS_URL`); without them the server starts with read + spec-authoring tools only.

### Session limits

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { MongoRedisGovernanceStore } from "./store.js";
// APPSMITH_MONGODB_URI=mongodb://127.0.0.1:27017 APPSMITH_REDIS_URL=redis://127.0.0.1:6379 \
// corepack yarn workspace appsmith-mcp test:unit src/governance/store.integration.test.ts
const mongoUrl =
process.env.APPSMITH_MONGODB_URI ?? process.env.APPSMITH_DB_URL;
process.env.APPSMITH_DB_URL || process.env.APPSMITH_MONGODB_URI;
const redisUrl = process.env.APPSMITH_REDIS_URL;
// Temporarily disabled: do not hit live Mongo/Redis from the MCP unit/CI run.
const describeIf = describe.skip;
Expand Down
10 changes: 10 additions & 0 deletions app/client/packages/mcp/src/governance/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ describe("createGovernanceStoreFromEnv", () => {
expect(createGovernanceStoreFromEnv()).toBeUndefined();
});

it("prefers APPSMITH_DB_URL over APPSMITH_MONGODB_URI when both are set", () => {
// A leftover docker.env Mongo URI must not override the product DB URL (Java/RTS order).
process.env.APPSMITH_DB_URL =
"postgresql://user:pass@localhost:5432/appsmith";
process.env.APPSMITH_MONGODB_URI = "mongodb://127.0.0.1:27017/appsmith";
process.env.APPSMITH_REDIS_URL = "redis://127.0.0.1:6379";

expect(createGovernanceStoreFromEnv()).toBeUndefined();
});

it("builds a store when the DB URL is a MongoDB URL", () => {
process.env.APPSMITH_MONGODB_URI = "mongodb://127.0.0.1:27017/appsmith";
process.env.APPSMITH_REDIS_URL = "redis://127.0.0.1:6379";
Expand Down
5 changes: 4 additions & 1 deletion app/client/packages/mcp/src/governance/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,11 @@ export function createRedisClientFromUrl(
export function createGovernanceStoreFromEnv():
| MongoRedisGovernanceStore
| undefined {
// Same precedence as Java (`appsmith.db.url=${APPSMITH_DB_URL:${APPSMITH_MONGODB_URI}}`) and RTS:
// product DB URL first, legacy Mongo URI only as fallback. Preferring MONGODB_URI used to ignore a
// real APPSMITH_DB_URL and still connect to a leftover localhost Mongo URI from docker.env.
const mongoUrl =
process.env.APPSMITH_MONGODB_URI ?? process.env.APPSMITH_DB_URL;
process.env.APPSMITH_DB_URL || process.env.APPSMITH_MONGODB_URI;
const redisUrl = process.env.APPSMITH_REDIS_URL;

if (!mongoUrl || !redisUrl) return undefined;
Expand Down
2 changes: 1 addition & 1 deletion app/client/packages/mcp/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ async function main(): Promise<void> {
process.stderr.write("Appsmith MCP governance store connected\n");
} else {
process.stderr.write(
"Appsmith MCP governance disabled (APPSMITH_MONGODB_URI/APPSMITH_DB_URL + APPSMITH_REDIS_URL not set); " +
"Appsmith MCP governance disabled (APPSMITH_DB_URL/APPSMITH_MONGODB_URI + APPSMITH_REDIS_URL not set); " +
"governed and destructive tools will not be registered\n",
);
}
Expand Down
22 changes: 0 additions & 22 deletions app/client/src/sagas/ActionExecution/PluginActionSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,6 @@ export default function* executePluginActionTriggerSaga(
);
}
} else {
AnalyticsUtil.logEvent("EXECUTE_ACTION_SUCCESS", actionExecutionAnalytics);
AppsmithConsole.info({
logType: LOG_TYPE.ACTION_EXECUTION_SUCCESS,
text: `Successfully executed in ${payload.duration}(ms)`,
Expand Down Expand Up @@ -1261,27 +1260,6 @@ function* executePageLoadAction(
: ActionExecutionContext.PAGE_LOAD,
});
} else {
AnalyticsUtil.logEvent("EXECUTE_ACTION_SUCCESS", {
type: pageAction.pluginType,
name: actionName,
pageId: pageId,
appMode: appMode,
appId: currentApp.id,
onPageLoad: true,
appName: currentApp.name,
environmentId: currentEnvDetails.id,
environmentName: currentEnvDetails.name,
isExampleApp: currentApp.appIsExample,
pluginName: plugin?.name,
datasourceId: datasourceId,
isMock: !!datasource?.isMock,
actionId: pageAction?.id,
inputParams: 0,
source: !!actionExecutionContext
? actionExecutionContext
: ActionExecutionContext.PAGE_LOAD,
});

yield take(ReduxActionTypes.SET_EVALUATED_TREE);
}
}
Expand Down
45 changes: 0 additions & 45 deletions app/client/src/sagas/DebuggerSagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,35 +651,6 @@ function* deleteDebuggerErrorLogsSaga(
} as LogDebuggerErrorAnalyticsPayload,
currentDebuggerErrors,
);

if (errorMessages) {
const currentEnvDetails: { id: string; name: string } = yield select(
getCurrentEnvironmentDetails,
);

//errorID has timestamp for 1:1 mapping with new and resolved errors
yield all(
errorMessages.map((errorMessage) => {
return fork(
logDebuggerErrorAnalyticsSaga,
{
...analyticsPayload,
environmentId: currentEnvDetails.id,
environmentName: currentEnvDetails.name,
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
errorId: generateErrorId(error),
errorMessage: errorMessage.message,
errorType: errorMessage.type,
errorSubType: errorMessage.subType,
appMode,
source: error.source,
logId: error.id,
} as LogDebuggerErrorAnalyticsPayload,
currentDebuggerErrors,
);
}),
);
}
}

const validErrorIds = validErrorPayloadsToDelete.map((payload) => payload.id);
Expand Down Expand Up @@ -812,22 +783,6 @@ function* activeFieldDebuggerErrorHandler(
} as LogDebuggerErrorAnalyticsPayload,
latestDebuggerErrors,
);

yield all(
initialSourceDebuggerError.messages?.map((errorMessage) => {
return fork(
logDebuggerErrorAnalyticsSaga,
{
...sourceMetaData,
...envMetaData,
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
errorMessage: errorMessage.message,
errorId: generateErrorId(initialSourceDebuggerError),
} as LogDebuggerErrorAnalyticsPayload,
latestDebuggerErrors,
);
}) || [],
);
}

if (latestSourceDebuggerError && initialSourceDebuggerError) {
Expand Down
50 changes: 47 additions & 3 deletions app/client/src/widgets/CustomWidget/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ const StyledLink = styled(Link)`
class CustomWidget extends BaseWidget<CustomWidgetProps, WidgetState> {
static type = "CUSTOM_WIDGET";

private modelUpdateCount = 0;
private modelUpdateLastEmitTime = 0;
private modelUpdateFlushTimer: ReturnType<typeof setTimeout> | null = null;
private static MODEL_UPDATE_THROTTLE_MS = 60000;

static getConfig() {
return {
name: "Custom",
Expand Down Expand Up @@ -421,11 +426,50 @@ class CustomWidget extends BaseWidget<CustomWidgetProps, WidgetState> {
...data,
});

AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
});
this.modelUpdateCount++;
const now = Date.now();

if (this.modelUpdateFlushTimer) {
clearTimeout(this.modelUpdateFlushTimer);
}

if (
now - this.modelUpdateLastEmitTime >=
CustomWidget.MODEL_UPDATE_THROTTLE_MS
) {
AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
updateCount: this.modelUpdateCount,
});
this.modelUpdateCount = 0;
this.modelUpdateLastEmitTime = now;
} else {
this.modelUpdateFlushTimer = setTimeout(() => {
if (this.modelUpdateCount > 0) {
AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
updateCount: this.modelUpdateCount,
});
this.modelUpdateCount = 0;
this.modelUpdateLastEmitTime = Date.now();
}
}, CustomWidget.MODEL_UPDATE_THROTTLE_MS);
}
};

componentWillUnmount() {
if (this.modelUpdateFlushTimer) {
clearTimeout(this.modelUpdateFlushTimer);
}

if (this.modelUpdateCount > 0) {
AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
updateCount: this.modelUpdateCount,
});
}
}

getRenderMode = () => {
switch (this.props.renderMode) {
case "CANVAS":
Expand Down
50 changes: 47 additions & 3 deletions app/client/src/widgets/wds/WDSCustomWidget/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export class WDSCustomWidget extends BaseWidget<
> {
static type = "WDS_CUSTOM_WIDGET";

private modelUpdateCount = 0;
private modelUpdateLastEmitTime = 0;
private modelUpdateFlushTimer: ReturnType<typeof setTimeout> | null = null;
private static MODEL_UPDATE_THROTTLE_MS = 60000;

static getConfig() {
return config.metaConfig;
}
Expand Down Expand Up @@ -102,11 +107,50 @@ export class WDSCustomWidget extends BaseWidget<
...data,
});

AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
});
this.modelUpdateCount++;
const now = Date.now();

if (this.modelUpdateFlushTimer) {
clearTimeout(this.modelUpdateFlushTimer);
}

if (
now - this.modelUpdateLastEmitTime >=
WDSCustomWidget.MODEL_UPDATE_THROTTLE_MS
) {
AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
updateCount: this.modelUpdateCount,
});
this.modelUpdateCount = 0;
this.modelUpdateLastEmitTime = now;
} else {
this.modelUpdateFlushTimer = setTimeout(() => {
if (this.modelUpdateCount > 0) {
AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
updateCount: this.modelUpdateCount,
});
this.modelUpdateCount = 0;
this.modelUpdateLastEmitTime = Date.now();
}
}, WDSCustomWidget.MODEL_UPDATE_THROTTLE_MS);
}
};

componentWillUnmount() {
if (this.modelUpdateFlushTimer) {
clearTimeout(this.modelUpdateFlushTimer);
}

if (this.modelUpdateCount > 0) {
AnalyticsUtil.logEvent("CUSTOM_WIDGET_API_UPDATE_MODEL", {
widgetId: this.props.widgetId,
updateCount: this.modelUpdateCount,
});
}
}

getRenderMode = () => {
switch (this.props.renderMode) {
case "CANVAS":
Expand Down
Loading
Loading