Merge pull request #31796 from appsmithorg/release

14th March, 2024 - Daily Promotion
This commit is contained in:
Trisha Anand 2024-03-14 16:54:56 +05:30 committed by GitHub
commit 35de20bac9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 391 additions and 156 deletions

View File

@ -1,5 +1,4 @@
import React from "react";
import type { Plugin } from "api/PluginApi";
import type { LogItemProps } from "components/editorComponents/Debugger/ErrorLogs/ErrorLogItem";
import { PluginType } from "entities/Action";
import WidgetIcon from "pages/Editor/Explorer/Widgets/WidgetIcon";
@ -13,7 +12,7 @@ import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
export const getIconForEntity: Record<
string,
(props: LogItemProps, pluginGroups: Record<string, Plugin>) => any
(props: LogItemProps, pluginImages: Record<string, string>) => any
> = {
[ENTITY_TYPE.WIDGET]: (props) => {
if (props.source?.pluginType) {
@ -25,19 +24,16 @@ export const getIconForEntity: Record<
[ENTITY_TYPE.JSACTION]: () => {
return JsFileIconV2(16, 16, true, true);
},
[ENTITY_TYPE.ACTION]: (props, pluginGroups) => {
[ENTITY_TYPE.ACTION]: (props, pluginImages) => {
const { iconId, source } = props;
if (source?.pluginType === PluginType.API && source.httpMethod) {
// If the source is an API action.
return ApiMethodIcon(source.httpMethod, "16px", "32px", 50);
} else if (iconId && pluginGroups[iconId]) {
} else if (iconId && pluginImages[iconId]) {
// If the source is a Datasource action.
return (
<EntityIcon height={"16px"} noBackground noBorder width={"16px"}>
<img
alt="entityIcon"
src={getAssetUrl(pluginGroups[iconId].iconLocation)}
/>
<img alt="entityIcon" src={getAssetUrl(pluginImages[iconId])} />
</EntityIcon>
);
}

View File

@ -20,8 +20,8 @@ import { BlankStateContainer } from "pages/Editor/IDE/EditorPane/JS/BlankStateCo
import { useCurrentEditorState } from "pages/Editor/IDE/hooks";
import history from "utils/history";
import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity";
import { getJSUrl } from "./utils";
import { useModuleOptions } from "@appsmith/utils/moduleInstanceHelpers";
import { getJSUrl } from "@appsmith/pages/Editor/IDE/EditorPane/JS/utils";
export const useJSAdd = () => {
const pageId = useSelector(getCurrentPageId);

View File

@ -20,7 +20,6 @@ import type { JSAction, Variable } from "entities/JSCollection";
import keyBy from "lodash/keyBy";
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
import { JsFileIconV2 } from "pages/Editor/Explorer/ExplorerIcons";
import { useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import type {
ActionData,
@ -69,9 +68,10 @@ import { setShowCreateNewModal } from "actions/propertyPaneActions";
import { setIdeEditorViewMode } from "actions/ideActions";
import { EditorViewMode } from "@appsmith/entities/IDE/constants";
import { getIsSideBySideEnabled } from "selectors/ideSelectors";
import { resolveIcon } from "pages/Editor/utils";
import { getModuleIcon, getPluginImagesFromPlugins } from "pages/Editor/utils";
import { getAllModules } from "@appsmith/selectors/modulesSelector";
import type { Module } from "@appsmith/constants/ModuleConstants";
import type { Plugin } from "api/PluginApi";
const actionList: {
label: string;
@ -396,7 +396,7 @@ export function useModalDropdownList(handleClose: () => void) {
export function getApiQueriesAndJSActionOptionsWithChildren(
pageId: string,
plugins: any,
plugins: Plugin[],
actions: ActionDataState,
jsActions: Array<JSCollectionData>,
dispatch: any,
@ -422,7 +422,7 @@ export function getApiQueriesAndJSActionOptionsWithChildren(
}
function getApiAndQueryOptions(
plugins: any,
plugins: Plugin[],
actions: ActionDataState,
dispatch: any,
handleClose: () => void,
@ -431,6 +431,8 @@ function getApiAndQueryOptions(
) {
const state = store.getState();
const isSideBySideEnabled = getIsSideBySideEnabled(state);
const pluginImages = getPluginImagesFromPlugins(plugins);
const pluginGroups: any = keyBy(plugins, "id");
const createQueryObject: TreeDropdownOption = {
label: "New query",
@ -474,7 +476,7 @@ function getApiAndQueryOptions(
type: queryOptions.value,
icon: getActionConfig(api.config.pluginType)?.getIcon(
api.config,
plugins[(api as any).config.datasource.pluginId],
pluginGroups[(api as any).config.datasource.pluginId],
api.config.pluginType === PluginType.API,
),
} as TreeDropdownOption);
@ -488,7 +490,7 @@ function getApiAndQueryOptions(
type: queryOptions.value,
icon: getActionConfig(query.config.pluginType)?.getIcon(
query.config,
plugins[(query as any).config.datasource.pluginId],
pluginGroups[(query as any).config.datasource.pluginId],
),
} as TreeDropdownOption);
});
@ -499,11 +501,7 @@ function getApiAndQueryOptions(
id: instance.config.id,
value: instance.config.name,
type: queryOptions.value,
icon: resolveIcon({
iconLocation: plugins[module.pluginId]?.iconLocation || "",
pluginType: module.pluginType,
moduleType: module.type,
}),
icon: getModuleIcon(module, pluginImages),
} as TreeDropdownOption);
});
}
@ -629,7 +627,6 @@ export function useApisQueriesAndJsActionOptions(handleClose: () => void) {
const plugins = useSelector((state: AppState) => {
return state.entities.plugins.list;
});
const pluginGroups: any = useMemo(() => keyBy(plugins, "id"), [plugins]);
const actions = useSelector(getCurrentActions);
const jsActions = useSelector(getCurrentJSCollections);
const queryModuleInstances = useSelector(
@ -641,7 +638,7 @@ export function useApisQueriesAndJsActionOptions(handleClose: () => void) {
// this function gets all the Queries/API's/JS Objects and attaches it to actionList
return getApiQueriesAndJSActionOptionsWithChildren(
pageId,
pluginGroups,
plugins,
actions,
jsActions,
dispatch,

View File

@ -8,7 +8,7 @@ import { getPlugins } from "@appsmith/selectors/entitiesSelector";
import EntityLink from "../../EntityLink";
import { DebuggerLinkUI } from "components/editorComponents/Debugger/DebuggerEntityLink";
import { getIconForEntity } from "@appsmith/components/editorComponents/Debugger/ErrorLogs/getLogIconForEntity";
import type { Plugin } from "api/PluginApi";
import { getPluginImagesFromPlugins } from "pages/Editor/utils";
const EntityLinkWrapper = styled.div`
display: flex;
@ -30,11 +30,11 @@ const IconWrapper = styled.span`
`;
// This function is used to fetch the icon component for the entity link.
const getIcon = (props: LogItemProps, pluginGroups: Record<string, Plugin>) => {
const getIcon = (props: LogItemProps, pluginImages: Record<string, string>) => {
const entityType = props.source?.type;
let icon = null;
if (entityType) {
icon = getIconForEntity[entityType](props, pluginGroups);
icon = getIconForEntity[entityType](props, pluginImages);
}
return icon || <img alt="icon" src={undefined} />;
};
@ -43,6 +43,7 @@ const getIcon = (props: LogItemProps, pluginGroups: Record<string, Plugin>) => {
export default function LogEntityLink(props: LogItemProps) {
const plugins = useSelector(getPlugins);
const pluginGroups = useMemo(() => keyBy(plugins, "id"), [plugins]);
const pluginImages = getPluginImagesFromPlugins(plugins);
const plugin = props.iconId ? pluginGroups[props.iconId] : undefined;
return (
@ -55,7 +56,7 @@ export default function LogEntityLink(props: LogItemProps) {
lineHeight: "14px",
}}
>
<IconWrapper>{getIcon(props, pluginGroups)}</IconWrapper>
<IconWrapper>{getIcon(props, pluginImages)}</IconWrapper>
<EntityLink
appsmithErrorCode={props.pluginErrorDetails?.appsmithErrorCode}
errorSubType={props.messages && props.messages[0].message.name}

View File

@ -27,9 +27,7 @@ import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import type { AppState } from "@appsmith/reducers";
import type { Module } from "@appsmith/constants/ModuleConstants";
import { getAllModules } from "@appsmith/selectors/modulesSelector";
import { resolveIcon } from "pages/Editor/utils";
import { Icon } from "design-system";
import { EntityIcon } from "pages/Editor/Explorer/ExplorerIcons";
import { getModuleIcon } from "pages/Editor/utils";
enum SortingWeights {
alphabetical = 1,
@ -105,19 +103,8 @@ export const getQueryIcon = (
if (query.config.hasOwnProperty("type")) {
const q = query as ModuleInstanceData;
const module = modules[q.config.sourceModuleId];
const icon = resolveIcon({
iconLocation: pluginImages[module.pluginId] || "",
pluginType: module.pluginType,
moduleType: module.type,
});
return (
icon || (
<EntityIcon>
<Icon name="module" />
</EntityIcon>
)
);
return getModuleIcon(module, pluginImages);
} else {
const action = query as ActionData;
return (

View File

@ -65,7 +65,7 @@ const SortingDropdownContainer = styled.div<{ size: string }>`
gap: 5px;
align-items: center;
> div {
width: 270px;
width: 250px;
}
${(props) =>
props.size === "small" &&
@ -168,6 +168,9 @@ function SortingComponent(props: any) {
<FormControl
config={{
...columnFieldConfig,
customStyles: {
width: "250px",
},
configProperty: `${columnPath}`,
nestedFormControl: true,
}}
@ -180,9 +183,9 @@ function SortingComponent(props: any) {
configProperty: `${OrderPath}`,
nestedFormControl: true,
customStyles: {
width: isBreakpointSmall ? "65px" : "270px",
width: isBreakpointSmall ? "65px" : "250px",
},
optionWidth: isBreakpointSmall ? "270px" : undefined,
optionWidth: isBreakpointSmall ? "250px" : undefined,
}}
formName={props.formName}
/>

View File

@ -22,7 +22,6 @@ import {
getPlugins,
} from "@appsmith/selectors/entitiesSelector";
import store from "store";
import keyBy from "lodash/keyBy";
import { getCurrentPageId } from "selectors/editorSelectors";
import { getApiQueriesAndJSActionOptionsWithChildren } from "components/editorComponents/ActionCreator/helpers";
import { selectEvaluationVersion } from "@appsmith/selectors/applicationSelectors";
@ -152,12 +151,11 @@ class ActionSelectorControl extends BaseControl<ControlProps> {
const pageId = getCurrentPageId(state);
const plugins = getPlugins(state);
const pluginGroups: any = keyBy(plugins, "id");
// this function gets all the Queries/API's/JS Objects and attaches it to actionList
const fieldOptions = getApiQueriesAndJSActionOptionsWithChildren(
pageId,
pluginGroups,
plugins,
actions,
jsCollections,
() => {

View File

@ -40,6 +40,7 @@ import ConvertEntityNotification from "@appsmith/pages/common/ConvertEntityNotif
import { useIsEditorPaneSegmentsEnabled } from "../IDE/hooks";
import { Icon } from "design-system";
import { resolveIcon } from "../utils";
import { ENTITY_ICON_SIZE, EntityIcon } from "../Explorer/ExplorerIcons";
type ApiEditorWrapperProps = RouteComponentProps<APIEditorRouteParams>;
@ -72,7 +73,14 @@ function ApiEditorWrapper(props: ApiEditorWrapperProps) {
iconLocation: pluginGroups[pluginId]?.iconLocation || "",
pluginType: action?.pluginType || "",
moduleType: action?.actionConfiguration?.body?.moduleType,
}) || <Icon name="module" />;
}) || (
<EntityIcon
height={`${ENTITY_ICON_SIZE}px`}
width={`${ENTITY_ICON_SIZE}px`}
>
<Icon name="module" />
</EntityIcon>
);
const isChangePermitted = getHasManageActionPermission(
isFeatureEnabled,

View File

@ -337,3 +337,11 @@ export function AppsmithAIIcon() {
export function ActionUrlIcon(url: string) {
return <img src={url} />;
}
export function DefaultModuleIcon() {
return (
<EntityIcon>
<Icon name="module" size="sm" />
</EntityIcon>
);
}

View File

@ -15,6 +15,7 @@ interface SettingsHeadingProps {
hasInfo?: boolean;
info?: string;
grow: boolean;
headingCount: number;
}
export interface OnUpdateSettingsProps {
@ -24,10 +25,14 @@ export interface OnUpdateSettingsProps {
}
interface SettingsItemProps {
headingCount: number;
action: JSAction;
disabled?: boolean;
onUpdateSettings?: (props: OnUpdateSettingsProps) => void;
renderAdditionalColumns?: (action: JSAction) => React.ReactNode;
renderAdditionalColumns?: (
action: JSAction,
headingCount: number,
) => React.ReactNode;
}
export interface JSFunctionSettingsProps {
@ -61,6 +66,7 @@ const StyledIcon = styled(Icon)`
`;
export const SettingColumn = styled.div<{
headingCount: number;
grow?: boolean;
isHeading?: boolean;
}>`
@ -68,13 +74,13 @@ export const SettingColumn = styled.div<{
align-items: center;
flex-grow: ${(props) => (props.grow ? 1 : 0)};
padding: 5px 12px;
min-width: 250px;
width: ${({ headingCount }) => `calc(100% / ${headingCount})`};
${(props) =>
props.isHeading &&
`
font-weight: ${props.theme.fontWeights[2]};
font-size: ${props.theme.fontSizes[2]}px
font-size: ${props.theme.fontSizes[2]}px;
margin-right: 9px;
`}
@ -91,8 +97,7 @@ const JSFunctionSettingsWrapper = styled.div`
const SettingsContainer = styled.div`
display: flex;
flex-direction: column;
width: max-content;
min-width: 700px;
width: 100%;
height: 100%;
& > h3 {
margin: 20px 0;
@ -116,9 +121,15 @@ const SettingsBodyWrapper = styled.div`
const SwitchWrapper = styled.div`
margin-left: 6ch;
`;
function SettingsHeading({ grow, hasInfo, info, text }: SettingsHeadingProps) {
function SettingsHeading({
grow,
hasInfo,
headingCount,
info,
text,
}: SettingsHeadingProps) {
return (
<SettingColumn grow={grow} isHeading>
<SettingColumn grow={grow} headingCount={headingCount} isHeading>
<span>{text}</span>
{hasInfo && info && (
<Tooltip content={createMessage(() => info)}>
@ -132,6 +143,7 @@ function SettingsHeading({ grow, hasInfo, info, text }: SettingsHeadingProps) {
function SettingsItem({
action,
disabled,
headingCount,
onUpdateSettings,
renderAdditionalColumns,
}: SettingsItemProps) {
@ -174,10 +186,13 @@ function SettingsItem({
className="t--async-js-function-settings"
id={`${action.name}-settings`}
>
<SettingColumn grow>
<SettingColumn grow headingCount={headingCount}>
<span>{action.name}</span>
</SettingColumn>
<SettingColumn className={`${action.name}-on-page-load-setting`}>
<SettingColumn
className={`${action.name}-on-page-load-setting`}
headingCount={headingCount}
>
{RADIO_OPTIONS.length > 2 ? (
<RadioGroup
defaultValue={executeOnPageLoad}
@ -207,7 +222,10 @@ function SettingsItem({
</SwitchWrapper>
)}
</SettingColumn>
<SettingColumn className={`${action.name}-confirm-before-execute`}>
<SettingColumn
className={`${action.name}-confirm-before-execute`}
headingCount={headingCount}
>
{RADIO_OPTIONS.length > 2 ? (
<RadioGroup
defaultValue={confirmBeforeExecute}
@ -238,7 +256,7 @@ function SettingsItem({
</SwitchWrapper>
)}
</SettingColumn>
{renderAdditionalColumns?.(action)}
{renderAdditionalColumns?.(action, headingCount)}
</SettingRow>
);
}
@ -250,6 +268,7 @@ function JSFunctionSettingsView({
onUpdateSettings,
renderAdditionalColumns,
}: JSFunctionSettingsProps) {
const headings = [...SETTINGS_HEADINGS, ...additionalHeadings];
return (
<JSFunctionSettingsWrapper>
<SettingsContainer>
@ -257,17 +276,16 @@ function JSFunctionSettingsView({
<SettingsRowWrapper>
<SettingsHeaderWrapper>
<SettingRow isHeading>
{[...SETTINGS_HEADINGS, ...additionalHeadings].map(
(setting, index) => (
<SettingsHeading
grow={index === 0}
hasInfo={setting.hasInfo}
info={setting.info}
key={setting.key}
text={setting.text}
/>
),
)}
{headings.map((setting, index) => (
<SettingsHeading
grow={index === 0}
hasInfo={setting.hasInfo}
headingCount={headings.length}
info={setting.info}
key={setting.key}
text={setting.text}
/>
))}
</SettingRow>
</SettingsHeaderWrapper>
<SettingsBodyWrapper>
@ -276,6 +294,7 @@ function JSFunctionSettingsView({
<SettingsItem
action={action}
disabled={disabled}
headingCount={headings.length}
key={action.id}
onUpdateSettings={onUpdateSettings}
renderAdditionalColumns={renderAdditionalColumns}
@ -283,7 +302,9 @@ function JSFunctionSettingsView({
))
) : (
<SettingRow noBorder>
<SettingColumn>{createMessage(NO_JS_FUNCTIONS)}</SettingColumn>
<SettingColumn headingCount={0}>
{createMessage(NO_JS_FUNCTIONS)}
</SettingColumn>
</SettingRow>
)}
</SettingsBodyWrapper>

View File

@ -41,6 +41,7 @@ import { PluginType } from "entities/Action";
import { useIsEditorPaneSegmentsEnabled } from "../IDE/hooks";
import { Icon } from "design-system";
import { resolveIcon } from "../utils";
import { ENTITY_ICON_SIZE, EntityIcon } from "../Explorer/ExplorerIcons";
type QueryEditorProps = RouteComponentProps<QueryEditorRouteParams>;
@ -66,7 +67,14 @@ function QueryEditor(props: QueryEditorProps) {
iconLocation: pluginImages[pluginId] || "",
pluginType: action?.pluginType || "",
moduleType: action?.actionConfiguration?.body?.moduleType,
}) || <Icon name="module" />;
}) || (
<EntityIcon
height={`${ENTITY_ICON_SIZE}px`}
width={`${ENTITY_ICON_SIZE}px`}
>
<Icon name="module" />
</EntityIcon>
);
const isDeletePermitted = getHasDeleteActionPermission(
isFeatureEnabled,

View File

@ -20,6 +20,7 @@ import { useSelector } from "react-redux";
import { getCurrentPageId } from "selectors/editorSelectors";
import type { WidgetCardProps } from "widgets/BaseWidget";
import type { ActionResponse } from "api/ActionAPI";
import type { Module } from "@appsmith/constants/ModuleConstants";
import { MODULE_TYPE } from "@appsmith/constants/ModuleConstants";
import {
ENTITY_ICON_SIZE,
@ -29,6 +30,9 @@ import {
} from "pages/Editor/Explorer/ExplorerIcons";
import { PluginType } from "entities/Action";
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
import type { Plugin } from "api/PluginApi";
import ImageAlt from "assets/images/placeholder-image.svg";
import { Icon } from "design-system";
export const draggableElement = (
id: string,
@ -381,3 +385,33 @@ export function resolveIcon({
return resolveQueryModuleIcon(iconLocation, pluginType, isLargeIcon);
}
}
export function getModuleIcon(
module: Module | undefined,
pluginImages: Record<string, string>,
isLargeIcon = false,
) {
return module ? (
resolveIcon({
iconLocation: pluginImages[module.pluginId] || "",
pluginType: module.pluginType,
moduleType: module.type,
isLargeIcon,
})
) : (
<EntityIcon
height={`${isLargeIcon ? ENTITY_ICON_SIZE * 2 : ENTITY_ICON_SIZE}px`}
width={`${isLargeIcon ? ENTITY_ICON_SIZE * 2 : ENTITY_ICON_SIZE}px`}
>
<Icon name="module" />
</EntityIcon>
);
}
export function getPluginImagesFromPlugins(plugins: Plugin[]) {
const pluginImages: Record<string, string> = {};
plugins.forEach((plugin) => {
pluginImages[plugin.id] = plugin?.iconLocation ?? ImageAlt;
});
return pluginImages;
}

View File

@ -16,9 +16,9 @@ import com.appsmith.external.models.Executable;
import com.appsmith.external.models.PluginType;
import com.appsmith.external.models.Policy;
import com.appsmith.external.models.Property;
import com.appsmith.external.views.ResponseOnly;
import com.appsmith.external.views.Views;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Getter;
import lombok.NoArgsConstructor;
@ -88,8 +88,9 @@ public class ActionCE_DTO implements Identifiable, Executable {
ActionConfiguration actionConfiguration;
// this attribute carries error messages while processing the actionCollection
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Transient
@JsonView(ResponseOnly.class)
@JsonView(Views.Public.class)
List<ErrorDTO> errorReports;
@JsonView(Views.Public.class)
@ -105,19 +106,23 @@ public class ActionCE_DTO implements Identifiable, Executable {
@JsonView(Views.Public.class)
List<Property> dynamicBindingPathList;
@JsonView(ResponseOnly.class)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
Boolean isValid;
@JsonView(ResponseOnly.class)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
Set<String> invalids;
@Transient
@JsonView(ResponseOnly.class)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
Set<String> messages = new HashSet<>();
// This is a list of keys that the client whose values the client needs to send during action execution.
// These are the Mustache keys that the server will replace before invoking the API
@JsonView(ResponseOnly.class)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
Set<String> jsonPathKeys;
@JsonView(Views.Internal.class)

View File

@ -1,7 +0,0 @@
package com.appsmith.external.views;
/**
* Intended to annotate fields that can be set by HTTP request payloads, but should NOT be included
* in HTTP responses sent back to the client.
*/
public interface RequestOnly extends Views.Public {}

View File

@ -1,8 +0,0 @@
package com.appsmith.external.views;
/**
* Intended to mark entity/DTO fields that should be included as part of HTTP responses, but should
* be ignored as part of HTTP requests. For example, if a field is marked with this annotation, in
* a class used with {@code @RequestBody}, it's value will NOT be deserialized.
*/
public interface ResponseOnly extends Views.Public {}

View File

@ -92,11 +92,7 @@
"isRequired": false,
"initialValue": "Appsmith",
"placeholderText": "Appsmith",
"hidden": {
"path": "datasourceConfiguration.properties[0].value",
"comparison": "NOT_EQUALS",
"value": "FORM_PROPERTIES_CONFIGURATION"
}
"hidden": true
},
{
"label": "JDBC URL",

View File

@ -45,6 +45,11 @@ public interface ActionCollectionServiceCE extends CrudService<ActionCollection,
Mono<ActionCollectionDTO> deleteUnpublishedActionCollection(String id);
Mono<ActionCollectionDTO> deleteUnpublishedActionCollectionWithOptionalPermission(
String id,
Optional<AclPermission> deleteCollectionPermission,
Optional<AclPermission> deleteActionPermission);
Mono<ActionCollectionDTO> deleteWithoutPermissionUnpublishedActionCollection(String id);
Mono<ActionCollectionDTO> deleteUnpublishedActionCollection(String id, String branchName);

View File

@ -356,16 +356,28 @@ public class ActionCollectionServiceCEImpl extends BaseService<ActionCollectionR
@Override
public Mono<ActionCollectionDTO> deleteWithoutPermissionUnpublishedActionCollection(String id) {
return deleteUnpublishedActionCollectionEx(id, Optional.empty());
return deleteUnpublishedActionCollectionEx(
id, Optional.empty(), Optional.of(actionPermission.getDeletePermission()));
}
@Override
public Mono<ActionCollectionDTO> deleteUnpublishedActionCollection(String id) {
return deleteUnpublishedActionCollectionEx(id, Optional.of(actionPermission.getDeletePermission()));
return deleteUnpublishedActionCollectionEx(
id,
Optional.of(actionPermission.getDeletePermission()),
Optional.of(actionPermission.getDeletePermission()));
}
@Override
public Mono<ActionCollectionDTO> deleteUnpublishedActionCollectionWithOptionalPermission(
String id,
Optional<AclPermission> deleteCollectionPermission,
Optional<AclPermission> deleteActionPermission) {
return deleteUnpublishedActionCollectionEx(id, deleteCollectionPermission, deleteActionPermission);
}
public Mono<ActionCollectionDTO> deleteUnpublishedActionCollectionEx(
String id, Optional<AclPermission> permission) {
String id, Optional<AclPermission> permission, Optional<AclPermission> deleteActionPermission) {
Mono<ActionCollection> actionCollectionMono = repository
.findById(id, permission)
.switchIfEmpty(Mono.error(
@ -381,7 +393,7 @@ public class ActionCollectionServiceCEImpl extends BaseService<ActionCollectionR
.getDefaultToBranchedActionIdsMap()
.values())
.flatMap(actionId -> newActionService
.deleteUnpublishedAction(actionId)
.deleteUnpublishedActionWithOptionalPermission(actionId, deleteActionPermission)
// return an empty action so that the filter can remove it from the list
.onErrorResume(throwable -> {
log.debug(

View File

@ -6,19 +6,23 @@ import com.appsmith.server.newactions.base.NewActionService;
import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.solutions.ActionExecutionSolution;
import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(Url.ACTION_URL)
@Slf4j
public class ActionController extends ActionControllerCE {
public ActionController(
LayoutActionService layoutActionService,
NewActionService newActionService,
RefactoringService refactoringService,
ActionExecutionSolution actionExecutionSolution) {
ActionExecutionSolution actionExecutionSolution,
ObservationRegistry observationRegistry) {
super(layoutActionService, newActionService, refactoringService, actionExecutionSolution);
super(layoutActionService, newActionService, refactoringService, actionExecutionSolution, observationRegistry);
}
}

View File

@ -2,7 +2,6 @@ package com.appsmith.server.controllers.ce;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.views.RequestOnly;
import com.appsmith.external.views.Views;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.Url;
@ -17,6 +16,7 @@ import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.solutions.ActionExecutionSolution;
import com.fasterxml.jackson.annotation.JsonView;
import io.micrometer.observation.ObservationRegistry;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -48,25 +48,30 @@ public class ActionControllerCE {
private final NewActionService newActionService;
private final RefactoringService refactoringService;
private final ActionExecutionSolution actionExecutionSolution;
private final ObservationRegistry observationRegistry;
@Autowired
public ActionControllerCE(
LayoutActionService layoutActionService,
NewActionService newActionService,
RefactoringService refactoringService,
ActionExecutionSolution actionExecutionSolution) {
ActionExecutionSolution actionExecutionSolution,
ObservationRegistry observationRegistry) {
this.layoutActionService = layoutActionService;
this.newActionService = newActionService;
this.refactoringService = refactoringService;
this.actionExecutionSolution = actionExecutionSolution;
this.observationRegistry = observationRegistry;
}
@JsonView(Views.Public.class)
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<ResponseDTO<ActionDTO>> createAction(
@Valid @RequestBody @JsonView(RequestOnly.class) ActionDTO resource,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
@Valid @RequestBody ActionDTO resource,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName,
@RequestHeader(name = "Origin", required = false) String originHeader,
ServerWebExchange exchange) {
log.debug("Going to create resource {}", resource.getClass().getName());
return layoutActionService
.createSingleActionWithBranch(resource, branchName)
@ -77,7 +82,7 @@ public class ActionControllerCE {
@PutMapping("/{defaultActionId}")
public Mono<ResponseDTO<ActionDTO>> updateAction(
@PathVariable String defaultActionId,
@Valid @RequestBody @JsonView(RequestOnly.class) ActionDTO resource,
@Valid @RequestBody ActionDTO resource,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
log.debug("Going to update resource with defaultActionId: {}, branch: {}", defaultActionId, branchName);
return layoutActionService
@ -173,6 +178,9 @@ public class ActionControllerCE {
* <p>
* The controller function is primarily used with param applicationId by the client to fetch the actions in edit
* mode.
*
* @param params
* @return
*/
@JsonView(Views.Public.class)
@GetMapping("")

View File

@ -29,6 +29,7 @@ import java.util.Set;
import static com.appsmith.server.constants.ResourceModes.EDIT;
import static com.appsmith.server.constants.ResourceModes.VIEW;
import static com.appsmith.server.helpers.DateUtils.ISO_FORMATTER;
import static com.appsmith.server.helpers.StringUtils.dotted;
@Getter
@Setter
@ -484,14 +485,14 @@ public class Application extends BaseDomain implements Artifact {
public static class Fields extends BaseDomain.Fields {
public static final String gitApplicationMetadata_gitAuth =
gitApplicationMetadata + "." + GitArtifactMetadata.Fields.gitAuth;
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.gitAuth);
public static final String gitApplicationMetadata_defaultApplicationId =
gitApplicationMetadata + "." + GitArtifactMetadata.Fields.defaultApplicationId;
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.defaultApplicationId);
public static final String gitApplicationMetadata_branchName =
gitApplicationMetadata + "." + GitArtifactMetadata.Fields.branchName;
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.branchName);
public static final String gitApplicationMetadata_isRepoPrivate =
gitApplicationMetadata + "." + GitArtifactMetadata.Fields.isRepoPrivate;
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isRepoPrivate);
public static final String gitApplicationMetadata_isProtectedBranch =
gitApplicationMetadata + "." + GitArtifactMetadata.Fields.isProtectedBranch;
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isProtectedBranch);
}
}

View File

@ -10,6 +10,8 @@ import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import static com.appsmith.server.helpers.StringUtils.dotted;
/**
* This class represents a collection of actions that may or may not belong to the same plugin.
* The logic for grouping is agnostic of the handling of this collection
@ -52,21 +54,21 @@ public class ActionCollectionCE extends BranchAwareDomain {
public static class Fields extends BranchAwareDomain.Fields {
public static final String publishedCollection_name =
publishedCollection + "." + ActionCollectionDTO.Fields.name;
dotted(publishedCollection, ActionCollectionDTO.Fields.name);
public static final String unpublishedCollection_name =
unpublishedCollection + "." + ActionCollectionDTO.Fields.name;
dotted(unpublishedCollection, ActionCollectionDTO.Fields.name);
public static final String publishedCollection_pageId =
publishedCollection + "." + ActionCollectionDTO.Fields.pageId;
dotted(publishedCollection, ActionCollectionDTO.Fields.pageId);
public static final String unpublishedCollection_pageId =
unpublishedCollection + "." + ActionCollectionDTO.Fields.pageId;
dotted(unpublishedCollection, ActionCollectionDTO.Fields.pageId);
public static final String publishedCollection_contextType =
publishedCollection + "." + ActionCollectionDTO.Fields.contextType;
dotted(publishedCollection, ActionCollectionDTO.Fields.contextType);
public static final String unpublishedCollection_contextType =
unpublishedCollection + "." + ActionCollectionDTO.Fields.contextType;
dotted(unpublishedCollection, ActionCollectionDTO.Fields.contextType);
public static final String unpublishedCollection_deletedAt =
unpublishedCollection + "." + ActionCollectionDTO.Fields.deletedAt;
dotted(unpublishedCollection, ActionCollectionDTO.Fields.deletedAt);
}
}

View File

@ -13,6 +13,8 @@ import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import static com.appsmith.server.helpers.StringUtils.dotted;
@Getter
@Setter
@ToString
@ -60,27 +62,23 @@ public class NewActionCE extends BranchAwareDomain {
public static class Fields extends BranchAwareDomain.Fields {
public static final String unpublishedAction_datasource_id =
String.join(".", unpublishedAction, ActionDTO.Fields.datasource, Datasource.Fields.id);
public static final String unpublishedAction_name = String.join(".", unpublishedAction, ActionDTO.Fields.name);
public static final String unpublishedAction_pageId =
String.join(".", unpublishedAction, ActionDTO.Fields.pageId);
public static final String unpublishedAction_deletedAt =
String.join(".", unpublishedAction, ActionDTO.Fields.deletedAt);
dotted(unpublishedAction, ActionDTO.Fields.datasource, Datasource.Fields.id);
public static final String unpublishedAction_name = dotted(unpublishedAction, ActionDTO.Fields.name);
public static final String unpublishedAction_pageId = dotted(unpublishedAction, ActionDTO.Fields.pageId);
public static final String unpublishedAction_deletedAt = dotted(unpublishedAction, ActionDTO.Fields.deletedAt);
public static final String unpublishedAction_contextType =
String.join(".", unpublishedAction, ActionDTO.Fields.contextType);
dotted(unpublishedAction, ActionDTO.Fields.contextType);
public static final String unpublishedAction_userSetOnLoad =
String.join(".", unpublishedAction, ActionDTO.Fields.userSetOnLoad);
dotted(unpublishedAction, ActionDTO.Fields.userSetOnLoad);
public static final String unpublishedAction_executeOnLoad =
String.join(".", unpublishedAction, ActionDTO.Fields.executeOnLoad);
dotted(unpublishedAction, ActionDTO.Fields.executeOnLoad);
public static final String unpublishedAction_fullyQualifiedName =
String.join(".", unpublishedAction, ActionDTO.Fields.fullyQualifiedName);
public static final String unpublishedAction_actionConfiguration_httpMethod = String.join(
".", unpublishedAction, ActionDTO.Fields.actionConfiguration, ActionConfiguration.Fields.httpMethod);
dotted(unpublishedAction, ActionDTO.Fields.fullyQualifiedName);
public static final String unpublishedAction_actionConfiguration_httpMethod =
dotted(unpublishedAction, ActionDTO.Fields.actionConfiguration, ActionConfiguration.Fields.httpMethod);
public static final String publishedAction_name = String.join(".", unpublishedAction, ActionDTO.Fields.name);
public static final String publishedAction_pageId =
String.join(".", unpublishedAction, ActionDTO.Fields.pageId);
public static final String publishedAction_contextType =
String.join(".", unpublishedAction, ActionDTO.Fields.contextType);
public static final String publishedAction_name = dotted(publishedAction, ActionDTO.Fields.name);
public static final String publishedAction_pageId = dotted(publishedAction, ActionDTO.Fields.pageId);
public static final String publishedAction_contextType = dotted(publishedAction, ActionDTO.Fields.contextType);
}
}

View File

@ -0,0 +1,9 @@
package com.appsmith.server.helpers;
public class StringUtils {
private StringUtils() {}
public static String dotted(String... parts) {
return String.join(".", parts);
}
}

View File

@ -82,6 +82,9 @@ public interface NewActionServiceCE extends CrudService<NewAction, String> {
Mono<ActionDTO> deleteUnpublishedAction(String id);
Mono<ActionDTO> deleteUnpublishedActionWithOptionalPermission(
String id, Optional<AclPermission> newActionDeletePermission);
Flux<ActionDTO> getUnpublishedActions(MultiValueMap<String, String> params, Boolean includeJsActions);
Flux<ActionDTO> getUnpublishedActions(

View File

@ -842,8 +842,14 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
@Override
public Mono<ActionDTO> deleteUnpublishedAction(String id) {
return deleteUnpublishedActionWithOptionalPermission(id, Optional.of(actionPermission.getDeletePermission()));
}
@Override
public Mono<ActionDTO> deleteUnpublishedActionWithOptionalPermission(
String id, Optional<AclPermission> newActionDeletePermission) {
Mono<NewAction> actionMono = repository
.findById(id, actionPermission.getDeletePermission())
.findById(id, newActionDeletePermission)
.switchIfEmpty(
Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, id)));
return actionMono

View File

@ -36,6 +36,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties;
@ -98,7 +99,7 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
log.info("Deleting {} actions which are no more used", invalidActionIds.size());
return Flux.fromIterable(invalidActionIds)
.flatMap(actionId -> newActionService
.deleteUnpublishedAction(actionId)
.deleteUnpublishedActionWithOptionalPermission(actionId, Optional.empty())
// return an empty action so that the filter can remove it from the list
.onErrorResume(throwable -> {
log.debug("Failed to delete action with id {} during import", actionId);

View File

@ -358,7 +358,14 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE<NewPa
// Delete the pages which were removed during git merge operation
// This does not apply to the traditional import via file approach
return Flux.fromIterable(invalidPageIds)
.flatMap(applicationPageService::deleteWithoutPermissionUnpublishedPage)
.flatMap(pageId -> {
return applicationPageService.deleteUnpublishedPageWithOptionalPermission(
pageId,
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty());
})
.flatMap(page -> newPageService
.archiveWithoutPermissionById(page.getId())
.onErrorResume(e -> {

View File

@ -1,5 +1,6 @@
package com.appsmith.server.services.ce;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.User;
@ -8,6 +9,8 @@ import com.appsmith.server.dtos.ClonePageMetaDTO;
import com.appsmith.server.dtos.PageDTO;
import reactor.core.publisher.Mono;
import java.util.Optional;
public interface ApplicationPageServiceCE {
Mono<PageDTO> createPage(PageDTO page);
@ -47,9 +50,14 @@ public interface ApplicationPageServiceCE {
Mono<PageDTO> deleteUnpublishedPageByBranchAndDefaultPageId(String defaultPageId, String branchName);
Mono<PageDTO> deleteUnpublishedPage(String id);
Mono<PageDTO> deleteUnpublishedPageWithOptionalPermission(
String id,
Optional<AclPermission> deletePagePermission,
Optional<AclPermission> readApplicationPermission,
Optional<AclPermission> deleteCollectionPermission,
Optional<AclPermission> deleteActionPermission);
Mono<PageDTO> deleteWithoutPermissionUnpublishedPage(String id);
Mono<PageDTO> deleteUnpublishedPage(String id);
Mono<Application> publish(String applicationId, boolean isPublishedManually);

View File

@ -640,6 +640,10 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
return page;
}));
final Flux<ActionCollection> sourceActionCollectionsFlux = getCloneableActionCollections(pageId);
Flux<NewAction> sourceActionFlux = getCloneableActions(pageId);
return sourcePageMono
.flatMap(page -> {
clonePageMetaDTO.setBranchedSourcePageId(page.getId());
@ -738,6 +742,26 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
}));
}
protected Flux<ActionCollection> getCloneableActionCollections(String pageId) {
final Flux<ActionCollection> sourceActionCollectionsFlux = actionCollectionService.findByPageId(pageId);
return sourceActionCollectionsFlux;
}
protected Flux<NewAction> getCloneableActions(String pageId) {
Flux<NewAction> sourceActionFlux = newActionService
.findByPageId(pageId, actionPermission.getEditPermission())
// Set collection reference in actions to null to reset to the new application's collections later
.map(newAction -> {
if (newAction.getUnpublishedAction() != null) {
newAction.getUnpublishedAction().setCollectionId(null);
}
return newAction;
})
// In case there are no actions in the page being cloned, return empty
.switchIfEmpty(Flux.empty());
return sourceActionFlux;
}
private Mono<PageDTO> clonePageGivenApplicationId(String pageId, String applicationId) {
final ClonePageMetaDTO clonePageMetaDTO = new ClonePageMetaDTO();
return clonePageGivenApplicationId(pageId, applicationId, null, clonePageMetaDTO);
@ -901,17 +925,38 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
* In this scenario, if we were to delete all actions associated with the page, we would end up deleting an action
* which is currently in published state and is being used.
*
* @param id The pageId which needs to be archived.
* @param id The pageId which needs to be archived.
* @param deletePagePermission
* @return
*/
@Override
public Mono<PageDTO> deleteWithoutPermissionUnpublishedPage(String id) {
return deleteUnpublishedPageEx(id, Optional.empty());
public Mono<PageDTO> deleteUnpublishedPageWithOptionalPermission(
String id,
Optional<AclPermission> deletePagePermission,
Optional<AclPermission> readApplicationPermission,
Optional<AclPermission> deleteCollectionPermission,
Optional<AclPermission> deleteActionPermission) {
return deleteUnpublishedPageEx(
id,
deletePagePermission,
readApplicationPermission,
deleteCollectionPermission,
deleteActionPermission);
}
@Override
public Mono<PageDTO> deleteUnpublishedPage(String id) {
return deleteUnpublishedPageEx(id, Optional.of(pagePermission.getDeletePermission()));
Optional<AclPermission> deletePagePermission = Optional.of(pagePermission.getDeletePermission());
Optional<AclPermission> readApplicationPermission = Optional.of(applicationPermission.getReadPermission());
Optional<AclPermission> deleteCollectionPermission = Optional.of(actionPermission.getDeletePermission());
Optional<AclPermission> deleteActionPermission = Optional.of(actionPermission.getDeletePermission());
return deleteUnpublishedPageEx(
id,
deletePagePermission,
readApplicationPermission,
deleteCollectionPermission,
deleteActionPermission);
}
/**
@ -923,23 +968,36 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
* In this scenario, if we were to delete all actions associated with the page, we would end up deleting an action
* which is currently in published state and is being used.
*
* @param id The pageId which needs to be archived.
* @param id The pageId which needs to be archived.
* @param readApplicationPermission
* @param deleteCollectionPermission
* @param deleteActionPermission
* @return
*/
private Mono<PageDTO> deleteUnpublishedPageEx(String id, Optional<AclPermission> permission) {
private Mono<PageDTO> deleteUnpublishedPageEx(
String id,
Optional<AclPermission> deletePagePermission,
Optional<AclPermission> readApplicationPermission,
Optional<AclPermission> deleteCollectionPermission,
Optional<AclPermission> deleteActionPermission) {
return newPageService
.findById(id, permission)
.findById(id, deletePagePermission)
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE, id)))
.flatMap(page -> {
log.debug(
"Going to archive pageId: {} for applicationId: {}", page.getId(), page.getApplicationId());
// Application is accessed without any application permission over here.
// previously it was getting accessed only with read permission.
Mono<Application> applicationMono = applicationService
.getById(page.getApplicationId())
.findById(page.getApplicationId(), readApplicationPermission)
.switchIfEmpty(Mono.error(
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, id)))
.flatMap(application -> {
application.getPages().removeIf(p -> p.getId().equals(page.getId()));
return applicationService.save(application);
});
Mono<NewPage> newPageMono;
if (page.getPublishedPage() != null) {
PageDTO unpublishedPage = page.getUnpublishedPage();
@ -966,12 +1024,13 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
* condition for delete action
*/
Mono<List<ActionDTO>> archivedActionsMono = newActionService
.findByPageId(page.getId(), actionPermission.getDeletePermission())
.findByPageId(page.getId(), deleteActionPermission)
.filter(newAction -> !StringUtils.hasLength(
newAction.getUnpublishedAction().getCollectionId()))
.flatMap(action -> {
log.debug("Going to archive actionId: {} for applicationId: {}", action.getId(), id);
return newActionService.deleteUnpublishedAction(action.getId());
return newActionService.deleteUnpublishedActionWithOptionalPermission(
action.getId(), deleteActionPermission);
})
.collectList();
@ -985,8 +1044,8 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
"Going to archive actionCollectionId: {} for applicationId: {}",
actionCollection.getId(),
id);
return actionCollectionService.deleteUnpublishedActionCollection(
actionCollection.getId());
return actionCollectionService.deleteUnpublishedActionCollectionWithOptionalPermission(
actionCollection.getId(), deleteCollectionPermission, deleteActionPermission);
})
.collectList();

View File

@ -683,7 +683,7 @@ public class ActionCollectionServiceImplTest {
Mockito.when(actionCollectionRepository.findById(Mockito.any(), Mockito.<Optional<AclPermission>>any()))
.thenReturn(Mono.just(actionCollection));
Mockito.when(newActionService.deleteUnpublishedAction(Mockito.any()))
Mockito.when(newActionService.deleteUnpublishedActionWithOptionalPermission(Mockito.any(), Mockito.any()))
.thenReturn(Mono.just(
actionCollection.getUnpublishedCollection().getActions().get(0)));

View File

@ -0,0 +1,65 @@
import {promises as fs} from "fs";
import path from "path";
async function findInnerClassDefinitions(directory) {
try {
const files = await fs.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stats = await fs.stat(filePath);
if (stats.isDirectory()) {
await findInnerClassDefinitions(filePath);
} else if (path.extname(filePath) === '.java') {
await processJavaFile(filePath);
}
}
} catch (err) {
console.error(err);
}
}
async function processJavaFile(filePath) {
try {
const contents = await fs.readFile(filePath, 'utf8');
const innerClassRegex = /^ {4}([\w ]+?)\s+class\s+Fields\s+(extends (\w+)\.Fields)?\s*{(.+?\n {4})?}$/gsm;
for (const innerClassMatch of contents.matchAll(innerClassRegex)) {
const classQualifiers = innerClassMatch[1]; // we don't care much about this
const expectedParentClass = innerClassMatch[3];
console.log(filePath, classQualifiers, expectedParentClass);
for (const match of innerClassMatch[0].matchAll(/\bpublic\s+static\s+final\s+String\s+(\w+)\s+=\s+(.+?);/gs)) {
const key = match[1]
const valMatcherParts = [`^dotted\\(`];
for (const [i, field] of key.split("_").entries()) {
if (i > 0) {
valMatcherParts.push(`\\s*,\\s+`);
}
valMatcherParts.push(`(\\w+\\.\\w+\\.)?${field}`);
}
valMatcherParts.push(`\\s*\\)$`);
const valMatcher = new RegExp(valMatcherParts.join(''));
if (!valMatcher.test(match[2])) {
console.log("key is", key);
console.log("val is", match[2]);
console.log("pattern", valMatcher);
console.error(`Field ${key} in ${filePath} is not looking right.`);
}
}
}
// if (finds.length > 1) {
// console.error(`Found multiple inner class definitions in file: ${filePath}`);
// return;
// }
} catch (err) {
console.error(err);
}
}
const directoryPath = '.';
findInnerClassDefinitions(directoryPath);