Merge branch 'release' into fix/dropdown-overlap-modal

This commit is contained in:
Tolulope Adetula 2021-05-24 08:21:30 +01:00
commit b95ff2001e
11 changed files with 184 additions and 68 deletions

View File

@ -216,6 +216,10 @@ export const CurrentValueViewer = memo(
},
collapsed: 2,
collapseStringsAfterLength: 20,
shouldCollapse: (field: any) => {
const index = field.name * 1;
return index >= 2 ? true : false;
},
};
content = (
<ReactJson src={props.evaluatedValue} {...reactJsonProps} />

View File

@ -1,4 +1,4 @@
import React, { Context, createContext, ReactNode } from "react";
import React, { Context, createContext, ReactNode, useMemo } from "react";
import { connect } from "react-redux";
import { WidgetOperation } from "widgets/BaseWidget";
@ -65,66 +65,58 @@ function EditorContextProvider(props: EditorContextProviderProps) {
updateWidgetMetaProperty,
updateWidgetProperty,
} = props;
// Memoize the context provider to prevent
// unnecessary renders
const contextValue = useMemo(
() => ({
executeAction,
updateWidget,
updateWidgetProperty,
updateWidgetMetaProperty,
disableDrag,
resetChildrenMetaProperty,
deleteWidgetProperty,
batchUpdateWidgetProperty,
}),
[
executeAction,
updateWidget,
updateWidgetProperty,
updateWidgetMetaProperty,
disableDrag,
resetChildrenMetaProperty,
deleteWidgetProperty,
batchUpdateWidgetProperty,
],
);
return (
<EditorContext.Provider
value={{
executeAction,
updateWidget,
updateWidgetProperty,
updateWidgetMetaProperty,
disableDrag,
resetChildrenMetaProperty,
deleteWidgetProperty,
batchUpdateWidgetProperty,
}}
>
<EditorContext.Provider value={contextValue}>
{children}
</EditorContext.Provider>
);
}
const mapDispatchToProps = (dispatch: any) => {
return {
updateWidgetProperty: (
widgetId: string,
propertyName: string,
propertyValue: any,
) =>
dispatch(
updateWidgetPropertyRequest(
widgetId,
propertyName,
propertyValue,
RenderModes.CANVAS,
),
),
executeAction: (actionPayload: ExecuteActionPayload) =>
dispatch(executeAction(actionPayload)),
updateWidget: (
operation: WidgetOperation,
widgetId: string,
payload: any,
) => dispatch(updateWidget(operation, widgetId, payload)),
updateWidgetMetaProperty: (
widgetId: string,
propertyName: string,
propertyValue: any,
) =>
dispatch(updateWidgetMetaProperty(widgetId, propertyName, propertyValue)),
resetChildrenMetaProperty: (widgetId: string) =>
dispatch(resetChildrenMetaProperty(widgetId)),
disableDrag: (disable: boolean) => {
dispatch(disableDragAction(disable));
},
deleteWidgetProperty: (widgetId: string, propertyPaths: string[]) =>
dispatch(deletePropertyAction(widgetId, propertyPaths)),
batchUpdateWidgetProperty: (
widgetId: string,
updates: BatchPropertyUpdatePayload,
) => {
dispatch(batchUpdatePropertyAction(widgetId, updates));
},
};
const mapDispatchToProps = {
updateWidgetProperty: (
widgetId: string,
propertyName: string,
propertyValue: any,
) =>
updateWidgetPropertyRequest(
widgetId,
propertyName,
propertyValue,
RenderModes.CANVAS,
),
executeAction,
updateWidget,
updateWidgetMetaProperty,
resetChildrenMetaProperty,
disableDrag: disableDragAction,
deleteWidgetProperty: deletePropertyAction,
batchUpdateWidgetProperty: batchUpdatePropertyAction,
};
export default connect(null, mapDispatchToProps)(EditorContextProvider);

View File

@ -43,8 +43,9 @@ export type ResizableComponentProps = WidgetProps & {
paddingOffset: number;
};
/* eslint-disable react/display-name */
export const ResizableComponent = memo((props: ResizableComponentProps) => {
export const ResizableComponent = memo(function ResizableComponent(
props: ResizableComponentProps,
) {
const resizableRef = useRef<HTMLDivElement>(null);
// Fetch information from the context
const { updateWidget } = useContext(EditorContext);
@ -109,15 +110,18 @@ export const ResizableComponent = memo((props: ResizableComponentProps) => {
possibleBoundingElements.length > 0
? possibleBoundingElements[0]
: undefined;
const boundingElementClientRect = boundingElement
? boundingElement.getBoundingClientRect()
: undefined;
// onResize handler
// Checks if the current resize position has any collisions
// If yes, set isColliding flag to true.
// If no, set isColliding flag to false.
const isColliding = (newDimensions: UIElementSize, position: XYCoord) => {
// Moving the bounding element calculations inside
// to make this expensive operation only whne
const boundingElementClientRect = boundingElement
? boundingElement.getBoundingClientRect()
: undefined;
const bottom =
props.topRow +
position.y / props.parentRowSpace +

View File

@ -26,7 +26,11 @@ import AppErrorBoundary from "./AppErrorBoundry";
import GlobalStyles from "globalStyles";
appInitializer();
import useRemoveSignUpCompleteParam from "utils/hooks/useRemoveSignUpCompleteParam";
function App() {
useRemoveSignUpCompleteParam();
return (
<Sentry.ErrorBoundary fallback={"An error has occured"}>
<Provider store={store}>

View File

@ -0,0 +1,21 @@
import { useEffect } from "react";
import history from "utils/history";
const useRemoveSignUpCompleteParam = () => {
useEffect(() => {
if (window.location.href) {
const url = new URL(window.location.href);
const searchParams = url.searchParams;
if (searchParams.get("isFromSignup")) {
searchParams.delete("isFromSignup");
history.replace({
pathname: url.pathname,
search: url.search,
hash: url.hash,
});
}
}
}, []);
};
export default useRemoveSignUpCompleteParam;

View File

@ -10,6 +10,7 @@ public enum AnalyticsEvents {
EXECUTE_ACTION("execute_ACTION_TRIGGERED"),
UPDATE_LAYOUT,
PUBLISH_APPLICATION("publish_APPLICATION"),
FORK
;
private final String eventName;

View File

@ -48,6 +48,9 @@ import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIO
@Service
public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Datasource, String> implements DatasourceService {
private static final String LOCALHOST_STRING = "localhost";
private static final String LOCALHOST_IP = "127.0.0.1";
private final OrganizationService organizationService;
private final SessionUserService sessionUserService;
private final PluginService pluginService;
@ -134,6 +137,19 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
});
}
private boolean endpointContainsLocalhost(Endpoint endpoint) {
if (endpoint == null || StringUtils.isEmpty(endpoint.getHost())) {
return false;
}
String host = endpoint.getHost().toLowerCase();
if (host.contains(LOCALHOST_STRING) || host.contains(LOCALHOST_IP)) {
return true;
}
return false;
}
public Mono<Datasource> populateHintMessages(Datasource datasource) {
if(datasource == null) {
@ -157,7 +173,7 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
.getDatasourceConfiguration()
.getEndpoints()
.stream()
.anyMatch(endpoint -> endpoint.getHost().contains("localhost"));
.anyMatch(endpoint -> endpointContainsLocalhost(endpoint));
}
if(usingLocalhostUrl) {

View File

@ -756,12 +756,14 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
.flatMap(application -> Mono.zip(
Mono.just(application),
sessionUserService.getCurrentUser(),
newPageService.getNameByPageId(actionDTO.getPageId(), viewMode)
newPageService.getNameByPageId(actionDTO.getPageId(), viewMode),
pluginService.getById(action.getPluginId())
))
.map(tuple -> {
final Application application = tuple.getT1();
final User user = tuple.getT2();
final String pageName = tuple.getT3();
final Plugin plugin = tuple.getT4();
final PluginType pluginType = action.getPluginType();
final Map<String, Object> data = new HashMap<>();
@ -769,6 +771,7 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
data.putAll(Map.of(
"username", user.getUsername(),
"type", pluginType,
"pluginName", plugin.getName(),
"name", actionDTO.getName(),
"datasource", Map.of(
"name", datasource.getName()
@ -777,11 +780,11 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
"appId", action.getApplicationId(),
"appMode", TRUE.equals(viewMode) ? "view" : "edit",
"appName", application.getName(),
"isExampleApp", application.isAppIsExample(),
"request", request
"isExampleApp", application.isAppIsExample()
));
data.putAll(Map.of(
"request", request,
"pageId", ObjectUtils.defaultIfNull(actionDTO.getPageId(), ""),
"pageName", pageName,
"isSuccessfulExecution", ObjectUtils.defaultIfNull(actionExecutionResult.getIsExecutionSuccess(), false),

View File

@ -1,24 +1,29 @@
package com.appsmith.server.solutions;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.constants.AnalyticsEvents;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.PolicyUtils;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SessionUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
public class ApplicationForkingService {
private final ApplicationService applicationService;
@ -26,6 +31,7 @@ public class ApplicationForkingService {
private final ExamplesOrganizationCloner examplesOrganizationCloner;
private final PolicyUtils policyUtils;
private final SessionUserService sessionUserService;
private final AnalyticsService analyticsService;
public Mono<Application> forkApplicationToOrganization(String applicationId, String organizationId) {
final Mono<Application> sourceApplicationMono = applicationService.findById(applicationId, AclPermission.READ_APPLICATIONS)
@ -34,7 +40,9 @@ public class ApplicationForkingService {
final Mono<Organization> targetOrganizationMono = organizationService.findById(organizationId, AclPermission.ORGANIZATION_MANAGE_APPLICATIONS)
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, "organization", organizationId)));
return Mono.zip(sourceApplicationMono, targetOrganizationMono, sessionUserService.getCurrentUser())
Mono<User> userMono = sessionUserService.getCurrentUser();
return Mono.zip(sourceApplicationMono, targetOrganizationMono, userMono)
.flatMap(tuple -> {
final Application application = tuple.getT1();
final Organization targetOrganization = tuple.getT2();
@ -58,8 +66,29 @@ public class ApplicationForkingService {
})
.flatMap(applicationIds -> {
final String newApplicationId = applicationIds.get(0);
return applicationService.getById(newApplicationId);
return applicationService.getById(newApplicationId)
.flatMap(application ->
sendForkApplicationAnalyticsEvent(applicationId, organizationId, application));
});
}
private Mono<Application> sendForkApplicationAnalyticsEvent(String applicationId, String orgId, Application application) {
return applicationService.findById(applicationId, AclPermission.READ_APPLICATIONS)
.flatMap(sourceApplication -> {
final Map<String, Object> data = Map.of(
"forkedFromAppId", applicationId,
"forkedToOrgId", orgId,
"forkedFromAppName", sourceApplication.getName()
);
return analyticsService.sendObjectEvent(AnalyticsEvents.FORK, application, data);
})
.onErrorResume(e -> {
log.warn("Error sending action execution data point", e);
return Mono.just(application);
});
}
}

View File

@ -984,7 +984,7 @@ public class DatasourceServiceTest {
@Test
@WithUserDetails(value = "api_user")
public void testHintMessageOnLocalhostUrlOnUpdateEventOnNonApiDatasource() {
public void testHintMessageOnLocalhostIPAddressOnUpdateEventOnNonApiDatasource() {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
Datasource datasource = new Datasource();
@ -1012,7 +1012,7 @@ public class DatasourceServiceTest {
DatasourceConfiguration datasourceConfiguration1 = new DatasourceConfiguration();
Connection connection1 = new Connection();
datasourceConfiguration1.setConnection(connection1);
Endpoint endpoint = new Endpoint("http://localhost", 0L);
Endpoint endpoint = new Endpoint("http://127.0.0.1/xyz", 0L);
datasourceConfiguration1.setEndpoints(new ArrayList<>());
datasourceConfiguration1.getEndpoints().add(endpoint);
updates.setDatasourceConfiguration(datasourceConfiguration1);
@ -1034,4 +1034,36 @@ public class DatasourceServiceTest {
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void testHintMessageNPE() {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
Mono<Plugin> pluginMono = pluginService.findByName("Installed Plugin Name");
Datasource datasource = new Datasource();
datasource.setName("NPE check");
datasource.setOrganizationId(orgId);
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
datasourceConfiguration.setEndpoints(new ArrayList<>());
Endpoint nullEndpoint = null;
datasourceConfiguration.getEndpoints().add(nullEndpoint);
Endpoint nullHost = new Endpoint(null, 0L);
datasourceConfiguration.getEndpoints().add(nullHost);
datasource.setDatasourceConfiguration(datasourceConfiguration);
Mono<Datasource> datasourceMono = pluginMono.map(plugin -> {
datasource.setPluginId(plugin.getId());
return datasource;
}).flatMap(datasourceService::create);
StepVerifier
.create(datasourceMono)
.assertNext(createdDatasource -> {
assertThat(createdDatasource.getMessages()).isEmpty();
})
.verifyComplete();
}
}

View File

@ -28,6 +28,16 @@ You can find the archives of the calls below with a brief summary of each sessio
## Archives
<strong>Appsmith Live Demo #3, 29th May 2021: Stitching APIs together to automate workflows</strong>
<a href = "https://youtu.be/_8qLbyWwXeA">Video Link</a>
#### Summary
Confidence shows the community how we internally use APIs from multiple services to build apps that simplifies our workflow within Appsmith. Top questions from members of our community was also discussed.
------------------
<strong>Office Hours May 13th, 2021: Product roadmap discussion</strong>
<a href = "https://youtu.be/1sw84i_M4_Y">Video Link</a>