Merge pull request #36337 from appsmithorg/release

16/09 Daily Promotion
This commit is contained in:
Aparna Ramachandran 2024-09-16 13:32:11 +05:30 committed by GitHub
commit d52aa53bb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 327 additions and 53 deletions

View File

@ -163,7 +163,7 @@
"node-forge": "^1.3.0",
"normalizr": "^3.3.0",
"object-hash": "^3.0.0",
"path-to-regexp": "^6.2.0",
"path-to-regexp": "^6.3.0",
"popper.js": "^1.15.0",
"prismjs": "^1.27.0",
"proxy-memoize": "^1.2.0",

View File

@ -1,7 +1,11 @@
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 { useDispatch } from "react-redux";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { runAction } from "../../actions/pluginActionActions";
interface PluginActionToolbarProps {
runOptions?: React.ReactNode;
@ -10,6 +14,25 @@ interface PluginActionToolbarProps {
}
const PluginActionToolbar = (props: PluginActionToolbarProps) => {
const { action, datasource, plugin } = usePluginActionContext();
const dispatch = useDispatch();
const handleRunClick = useCallback(() => {
AnalyticsUtil.logEvent("RUN_QUERY_CLICK", {
actionName: action.name,
actionId: action.id,
pluginName: plugin.name,
datasourceId: datasource?.id,
isMock: datasource?.isMock,
});
dispatch(runAction(action.id));
}, [
action.id,
action.name,
datasource?.id,
datasource?.isMock,
dispatch,
plugin.name,
]);
return (
<IDEToolbar>
<IDEToolbar.Left>{props.children}</IDEToolbar.Left>
@ -20,7 +43,7 @@ const PluginActionToolbar = (props: PluginActionToolbarProps) => {
placement="topRight"
showArrow={false}
>
<Button kind="primary" size="sm">
<Button kind="primary" onClick={handleRunClick} size="sm">
Run
</Button>
</Tooltip>
@ -30,7 +53,7 @@ const PluginActionToolbar = (props: PluginActionToolbarProps) => {
size="sm"
startIcon="settings-2-line"
/>
<Menu>
<Menu key={action.id}>
<MenuTrigger>
<Button
isIconButton
@ -39,7 +62,12 @@ const PluginActionToolbar = (props: PluginActionToolbarProps) => {
startIcon="more-2-fill"
/>
</MenuTrigger>
<MenuContent loop style={{ zIndex: 100 }} width="200px">
<MenuContent
key={action.id}
loop
style={{ zIndex: 100 }}
width="200px"
>
{props.menuContent}
</MenuContent>
</Menu>

View File

@ -1,9 +1,9 @@
import React from "react";
import { PluginActionToolbar } from "PluginActionEditor";
import { ConvertToModuleCTA } from "./ConvertToModule";
import AppPluginActionMenu from "./PluginActionMoreActions";
const AppPluginActionToolbar = () => {
return <PluginActionToolbar menuContent={<ConvertToModuleCTA />} />;
return <PluginActionToolbar menuContent={<AppPluginActionMenu />} />;
};
export default AppPluginActionToolbar;

View File

@ -0,0 +1,183 @@
import React, { useCallback, useMemo, useState } from "react";
import {
getHasDeleteActionPermission,
getHasManageActionPermission,
} from "ee/utils/BusinessFeatures/permissionPageHelpers";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import { usePluginActionContext } from "PluginActionEditor";
import {
MenuItem,
MenuSub,
MenuSubContent,
MenuSubTrigger,
} from "@appsmith/ads";
import {
CONFIRM_CONTEXT_DELETE,
CONTEXT_COPY,
CONTEXT_DELETE,
CONTEXT_MOVE,
createMessage,
} from "ee/constants/messages";
import { useDispatch, useSelector } from "react-redux";
import {
copyActionRequest,
deleteAction,
moveActionRequest,
} from "actions/pluginActionActions";
import { getCurrentPageId } from "selectors/editorSelectors";
import type { Page } from "entities/Page";
import { getPageList } from "ee/selectors/entitiesSelector";
import { ConvertToModuleCTA } from "./ConvertToModule";
const PageMenuItem = (props: {
page: Page;
onSelect: (id: string) => void;
}) => {
const handleOnSelect = useCallback(() => {
props.onSelect(props.page.pageId);
}, [props]);
return <MenuItem onSelect={handleOnSelect}>{props.page.pageName}</MenuItem>;
};
const Copy = () => {
const menuPages = useSelector(getPageList);
const { action } = usePluginActionContext();
const dispatch = useDispatch();
const copyActionToPage = useCallback(
(pageId: string) =>
dispatch(
copyActionRequest({
id: action.id,
destinationPageId: pageId,
name: action.name,
}),
),
[action.id, action.name, dispatch],
);
return (
<MenuSub>
<MenuSubTrigger startIcon="duplicate">
{createMessage(CONTEXT_COPY)}
</MenuSubTrigger>
<MenuSubContent>
{menuPages.map((page) => {
return (
<PageMenuItem
key={page.basePageId}
onSelect={copyActionToPage}
page={page}
/>
);
})}
</MenuSubContent>
</MenuSub>
);
};
const Move = () => {
const dispatch = useDispatch();
const { action } = usePluginActionContext();
const currentPageId = useSelector(getCurrentPageId);
const allPages = useSelector(getPageList);
const menuPages = useMemo(() => {
return allPages.filter((page) => page.pageId !== currentPageId);
}, [allPages, currentPageId]);
const moveActionToPage = useCallback(
(destinationPageId: string) =>
dispatch(
moveActionRequest({
id: action.id,
destinationPageId,
originalPageId: currentPageId,
name: action.name,
}),
),
[dispatch, action.id, action.name, currentPageId],
);
return (
<MenuSub>
<MenuSubTrigger startIcon="swap-horizontal">
{createMessage(CONTEXT_MOVE)}
</MenuSubTrigger>
<MenuSubContent>
{menuPages.length > 1 ? (
menuPages.map((page) => {
return (
<PageMenuItem
key={page.basePageId}
onSelect={moveActionToPage}
page={page}
/>
);
})
) : (
<MenuItem key="no-pages">No pages</MenuItem>
)}
</MenuSubContent>
</MenuSub>
);
};
const Delete = () => {
const dispatch = useDispatch();
const { action } = usePluginActionContext();
const [confirmDelete, setConfirmDelete] = useState(false);
const deleteActionFromPage = useCallback(() => {
dispatch(deleteAction({ id: action.id, name: action.name }));
}, [action.id, action.name, dispatch]);
const handleSelect = useCallback(() => {
confirmDelete ? deleteActionFromPage() : setConfirmDelete(true);
}, [confirmDelete, deleteActionFromPage]);
const menuLabel = confirmDelete
? createMessage(CONFIRM_CONTEXT_DELETE)
: createMessage(CONTEXT_DELETE);
return (
<MenuItem
className="t--apiFormDeleteBtn error-menuitem"
onSelect={handleSelect}
startIcon="trash"
>
{menuLabel}
</MenuItem>
);
};
const AppPluginActionMenu = () => {
const { action } = usePluginActionContext();
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const isChangePermitted = getHasManageActionPermission(
isFeatureEnabled,
action.userPermissions,
);
const isDeletePermitted = getHasDeleteActionPermission(
isFeatureEnabled,
action?.userPermissions,
);
return (
<>
<ConvertToModuleCTA />
{isChangePermitted && (
<>
<Copy />
<Move />
</>
)}
{isDeletePermitted && <Delete />}
</>
);
};
export default AppPluginActionMenu;

View File

@ -13391,7 +13391,7 @@ __metadata:
node-forge: ^1.3.0
normalizr: ^3.3.0
object-hash: ^3.0.0
path-to-regexp: ^6.2.0
path-to-regexp: ^6.3.0
pg: ^8.11.3
plop: ^3.1.1
popper.js: ^1.15.0
@ -27112,10 +27112,10 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:^6.2.0":
version: 6.2.0
resolution: "path-to-regexp@npm:6.2.0"
checksum: a6aca74d2d6e2e7594d812f653cf85e9cb5054d3a8d80f099722a44ef6ad22639b02078e5ea83d11db16321c3e4359e3f1ab0274fa78dad0754a6e53f630b0fc
"path-to-regexp@npm:^6.3.0":
version: 6.3.0
resolution: "path-to-regexp@npm:6.3.0"
checksum: eca78602e6434a1b6799d511d375ec044e8d7e28f5a48aa5c28d57d8152fb52f3fc62fb1cfc5dfa2198e1f041c2a82ed14043d75740a2fe60e91b5089a153250
languageName: node
linkType: hard

View File

@ -34,8 +34,10 @@ public class DatasourceConfiguration implements AppsmithDomain {
@JsonView({Views.Public.class, FromRequest.class})
AuthenticationDTO authentication;
@JsonView({Views.Public.class, FromRequest.class})
SSHConnection sshProxy;
@JsonView({Views.Public.class, FromRequest.class})
Boolean sshProxyEnabled;
@JsonView({Views.Public.class, FromRequest.class, Git.class})

View File

@ -67,28 +67,38 @@ public class JsonSchemaMigration {
// TODO: make import flow migration reactive
return Mono.just(migrateServerSchema(appJson))
.flatMap(migratedApplicationJson -> {
if (migratedApplicationJson.getServerSchemaVersion() == 9
&& Boolean.TRUE.equals(MigrationHelperMethods.doesRestApiRequireMigration(
migratedApplicationJson))) {
return jsonSchemaMigrationHelper
.addDatasourceConfigurationToDefaultRestApiActions(
baseApplicationId, branchName, migratedApplicationJson)
.map(applicationJsonWithMigration10 -> {
applicationJsonWithMigration10.setServerSchemaVersion(10);
return applicationJsonWithMigration10;
});
// In Server version 9, there was a bug where the Embedded REST API datasource URL
// was not being persisted correctly. Once the bug was fixed,
// any previously uncommitted changes started appearing as uncommitted modifications
// in the apps. To automatically commit these changes
// (which were now appearing as uncommitted), a migration process was needed.
// This migration fetches the datasource URL from the database
// and serializes it in Git if the URL exists.
// If the URL is missing, it copies the empty datasource configuration
// if the configuration is present in the database.
// Otherwise, it leaves the configuration unchanged.
// Due to an update in the migration logic after version 10 was shipped,
// the entire migration process was moved to version 11.
// This adjustment ensures that the same operation can be
// performed again for the changes introduced in version 10.
if (migratedApplicationJson.getServerSchemaVersion() == 9) {
migratedApplicationJson.setServerSchemaVersion(10);
}
if (migratedApplicationJson.getServerSchemaVersion() == 10) {
if (Boolean.TRUE.equals(MigrationHelperMethods.doesRestApiRequireMigration(
migratedApplicationJson))) {
return jsonSchemaMigrationHelper
.addDatasourceConfigurationToDefaultRestApiActions(
baseApplicationId, branchName, migratedApplicationJson);
}
migratedApplicationJson.setServerSchemaVersion(11);
}
migratedApplicationJson.setServerSchemaVersion(10);
return Mono.just(migratedApplicationJson);
})
.map(migratedAppJson -> {
if (applicationJson
.getServerSchemaVersion()
.equals(jsonSchemaVersions.getServerVersion())) {
return applicationJson;
}
applicationJson.setServerSchemaVersion(jsonSchemaVersions.getServerVersion());
return applicationJson;
});
@ -193,16 +203,14 @@ public class JsonSchemaMigration {
switch (applicationJson.getServerSchemaVersion()) {
case 9:
applicationJson.setServerSchemaVersion(10);
case 10:
// this if for cases where we have empty datasource configs
MigrationHelperMethods.migrateApplicationJsonToVersionTen(applicationJson, Map.of());
applicationJson.setServerSchemaVersion(10);
applicationJson.setServerSchemaVersion(11);
default:
}
if (applicationJson.getServerSchemaVersion().equals(jsonSchemaVersions.getServerVersion())) {
return applicationJson;
}
applicationJson.setServerSchemaVersion(jsonSchemaVersions.getServerVersion());
return applicationJson;
}

View File

@ -4,7 +4,7 @@ import org.springframework.stereotype.Component;
@Component
public class JsonSchemaVersionsFallback {
private static final Integer serverVersion = 10;
private static final Integer serverVersion = 11;
public static final Integer clientVersion = 1;
public Integer getServerVersion() {

View File

@ -44,6 +44,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.appsmith.external.constants.PluginConstants.PackageName.GRAPHQL_PLUGIN;
import static com.appsmith.external.constants.PluginConstants.PackageName.REST_API_PLUGIN;
import static com.appsmith.server.constants.ResourceModes.EDIT;
import static com.appsmith.server.constants.ResourceModes.VIEW;
import static org.springframework.data.mongodb.core.query.Criteria.where;
@ -1235,13 +1237,34 @@ public class MigrationHelperMethods {
}
}
private static boolean conditionForDefaultRestDatasourceMigration(NewAction action) {
public static boolean conditionForDefaultRestDatasource(NewAction action) {
if (action.getUnpublishedAction() == null
|| action.getUnpublishedAction().getDatasource() == null) {
return false;
}
Datasource actionDatasource = action.getUnpublishedAction().getDatasource();
// probable check for the default rest datasource action is.
// it has no datasource id and action's plugin id is either rest-api or graphql plugin.
boolean probableCheckForDefaultRestDatasource = !org.springframework.util.StringUtils.hasText(
actionDatasource.getId())
&& (REST_API_PLUGIN.equals(action.getPluginId()) || GRAPHQL_PLUGIN.equals(action.getPluginId()));
// condition to check if the action is default rest datasource.
// it has no datasource id and name is equal to DEFAULT_REST_DATASOURCE
boolean isActionDefaultRestDatasource = !org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginConstants.DEFAULT_REST_DATASOURCE.equals(actionDatasource.getName());
boolean certainCheckForDefaultRestDatasource =
!org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginConstants.DEFAULT_REST_DATASOURCE.equals(actionDatasource.getName());
// Two separate types of checks over here, it's either the obvious certain way to identify or
// the likely chance that the datasource is present.
return certainCheckForDefaultRestDatasource || probableCheckForDefaultRestDatasource;
}
private static boolean conditionForDefaultRestDatasourceMigration(NewAction action) {
boolean isActionDefaultRestDatasource = conditionForDefaultRestDatasource(action);
Datasource actionDatasource = action.getUnpublishedAction().getDatasource();
// condition to check if the action has missing url or has no config at all
boolean isDatasourceConfigurationOrUrlMissing = actionDatasource.getDatasourceConfiguration() == null
@ -1322,18 +1345,25 @@ public class MigrationHelperMethods {
if (defaultDatasourceActionMap.containsKey(action.getGitSyncId())) {
NewAction actionFromMap = defaultDatasourceActionMap.get(action.getGitSyncId());
// NPE check to avoid migration failures
if (actionFromMap.getUnpublishedAction() == null
|| actionFromMap.getUnpublishedAction().getDatasource() == null
|| actionFromMap.getUnpublishedAction().getDatasource().getDatasourceConfiguration() == null) {
return;
}
// set the datasource config in the json action only if the datasource config from db is not null,
// else it'll start to show as uncommited changes.
DatasourceConfiguration datasourceConfigurationFromDBAction =
actionFromMap.getUnpublishedAction().getDatasource().getDatasourceConfiguration();
if (datasourceConfigurationFromDBAction != null) {
datasourceConfiguration.setUrl(datasourceConfigurationFromDBAction.getUrl());
}
}
// At this point it's established that datasource config of db action is not null.
datasourceConfiguration.setUrl(datasourceConfigurationFromDBAction.getUrl());
actionDatasource.setDatasourceConfiguration(datasourceConfiguration);
if (!org.springframework.util.StringUtils.hasText(datasourceConfiguration.getUrl())) {
} else {
datasourceConfiguration.setUrl("");
actionDatasource.setDatasourceConfiguration(datasourceConfiguration);
}
actionDatasource.setDatasourceConfiguration(datasourceConfiguration);
}
}

View File

@ -1,6 +1,8 @@
package com.appsmith.server.migrations.utils;
import com.appsmith.external.constants.PluginConstants;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.PluginType;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.NewAction;
@ -51,14 +53,27 @@ public class JsonSchemaMigrationHelper {
return false;
}
boolean reverseFlag = StringUtils.hasText(action.getUnpublishedAction()
.getDatasource()
.getId())
|| !PluginConstants.DEFAULT_REST_DATASOURCE.equals(action.getUnpublishedAction()
.getDatasource()
.getName());
Datasource actionDatasource =
action.getUnpublishedAction().getDatasource();
return !reverseFlag;
// lenient probable check for the default rest datasource action is.
// As we don't have any harm in the allowing API actions present in db.
// it has no datasource id and action's plugin type is API
boolean probableCheckForDefaultRestDatasource =
!org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginType.API.equals(action.getPluginType());
// condition to check if the action is default rest datasource.
// it has no datasource id and name is equal to DEFAULT_REST_DATASOURCE
boolean certainCheckForDefaultRestDatasource =
!org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginConstants.DEFAULT_REST_DATASOURCE.equals(
actionDatasource.getName());
// Two separate types of checks over here, it's either the obvious certain way to
// identify or
// the likely chance that the datasource is present.
return certainCheckForDefaultRestDatasource || probableCheckForDefaultRestDatasource;
})
.collectMap(NewAction::getGitSyncId);
})

View File

@ -1,4 +1,4 @@
package com.appsmith.server.services;
package com.appsmith.server.services.ce;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.Datasource;
@ -32,6 +32,14 @@ import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.plugins.base.PluginService;
import com.appsmith.server.repositories.ApplicationRepository;
import com.appsmith.server.repositories.NewPageRepository;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ConsolidatedAPIService;
import com.appsmith.server.services.MockDataService;
import com.appsmith.server.services.ProductAlertService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
import com.appsmith.server.services.UserDataService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.themes.base.ThemeService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;