Merge pull request #31476 from appsmithorg/release
05/03 Daily promotion
2
.github/config.json
vendored
12
.github/workflows/server-build.yml
vendored
|
|
@ -182,17 +182,15 @@ jobs:
|
|||
fi
|
||||
if ! mvn test "${args[@]}"; then
|
||||
echo "Generating failed test list:"
|
||||
failed_tests_file="$PWD/failed-server-tests.txt"
|
||||
gawk -F\" '/<testcase / {cur_test = $4 "#" $2} /<failure / {print cur_test}' $(find . -type f -name 'TEST-*.xml') \
|
||||
| tee failed-server-tests.txt
|
||||
echo "Report files found:"
|
||||
find . -type f -name 'TEST-*.xml'
|
||||
echo "Test case failure lines:"
|
||||
grep -FoB1 '<failure ' $(find . -type f -name 'TEST-*.xml') || true
|
||||
if [[ -s failed-server-tests.txt ]]; then
|
||||
| tee "$failed_tests_file"
|
||||
echo "Saved to $failed_tests_file"
|
||||
if [[ -s $failed_tests_file ]]; then
|
||||
content="$(
|
||||
echo "## Failed server tests"
|
||||
echo
|
||||
sed 's/^/- /' 'failed-server-tests.txt' | sort -u
|
||||
sed 's/^/- /' "$failed_tests_file" | sort -u
|
||||
)"
|
||||
echo "$content" >> "$GITHUB_STEP_SUMMARY"
|
||||
# Add a comment to the PR with the list of failed tests.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ module.exports = {
|
|||
babel: {
|
||||
plugins: ["babel-plugin-lodash"],
|
||||
},
|
||||
eslint: {
|
||||
enable: false,
|
||||
},
|
||||
webpack: {
|
||||
configure: {
|
||||
resolve: {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,18 @@ module.exports = merge(common, {
|
|||
client: {
|
||||
overlay: {
|
||||
warnings: false,
|
||||
errors: false
|
||||
}
|
||||
}
|
||||
errors: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
cache: {
|
||||
type: "filesystem",
|
||||
memoryCacheUnaffected: true,
|
||||
},
|
||||
experiments: {
|
||||
cacheUnaffected: true,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"analyze": "yarn cra-bundle-analyzer",
|
||||
"start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_CLIENT_LOG_LEVEL=debug HOST=dev.appsmith.com craco --max_old_space_size=7168 start",
|
||||
"start": "BROWSER=none REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_CLIENT_LOG_LEVEL=debug HOST=dev.appsmith.com craco --max_old_space_size=4096 start",
|
||||
"build": "./build.sh",
|
||||
"build-airgap": "node download-assets.js && ./build.sh",
|
||||
"build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js",
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { Item, Menu, MenuList } from "../../Menu";
|
|||
import { useListState } from "@react-stately/list";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
import type { ActionGroupProps } from "./types";
|
||||
import type { ButtonGroupProps } from "../../../index";
|
||||
import { useActionGroup } from "./useActionGroup";
|
||||
import { IconButton } from "../../IconButton";
|
||||
import { ActionGroupItem } from "./ActionGroupItem";
|
||||
|
||||
const _ActionGroup = <T extends object>(
|
||||
props: ActionGroupProps<T>,
|
||||
props: ButtonGroupProps<T>,
|
||||
ref: DOMRef<HTMLDivElement>,
|
||||
) => {
|
||||
const {
|
||||
|
|
@ -52,7 +52,7 @@ const _ActionGroup = <T extends object>(
|
|||
>
|
||||
<div
|
||||
className={styles.actionGroup}
|
||||
data-density={density ? density : undefined}
|
||||
data-density={Boolean(density) ? density : undefined}
|
||||
data-orientation={orientation}
|
||||
data-overflow={overflowMode}
|
||||
ref={domRef}
|
||||
|
|
@ -60,8 +60,9 @@ const _ActionGroup = <T extends object>(
|
|||
{...others}
|
||||
>
|
||||
{children.map((item) => {
|
||||
if (Boolean(item.props.isSeparator))
|
||||
return <div data-separator="" />;
|
||||
if (Boolean(item.props.isSeparator)) {
|
||||
return <div data-separator="" key={item.key} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionGroupItem
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { Button } from "@design-system/widgets";
|
||||
|
||||
import type { ActionGroupItemProps } from "./types";
|
||||
import type { ButtonGroupItemProps } from "../../../";
|
||||
import type { ButtonRef as HeadlessButtonRef } from "@design-system/headless";
|
||||
|
||||
const _ActionGroupItem = <T extends object>(
|
||||
props: ActionGroupItemProps<T>,
|
||||
props: ButtonGroupItemProps<T>,
|
||||
ref: HeadlessButtonRef,
|
||||
) => {
|
||||
const { color, item, variant, ...rest } = props;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from "./types";
|
||||
export { ActionGroup } from "./ActionGroup";
|
||||
export { ActionGroupItem } from "./ActionGroupItem";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { ListState } from "@react-stately/list";
|
|||
import { useCallback, type RefObject, useMemo } from "react";
|
||||
import type { DOMAttributes, FocusableElement } from "@react-types/shared";
|
||||
|
||||
import type { ActionGroupProps } from "./types";
|
||||
import type { ButtonGroupProps } from "../../../";
|
||||
import {
|
||||
useLayoutEffect,
|
||||
useResizeObserver,
|
||||
|
|
@ -17,7 +17,7 @@ export interface ActionGroupAria {
|
|||
}
|
||||
|
||||
export function useActionGroup<T>(
|
||||
props: ActionGroupProps<T>,
|
||||
props: ButtonGroupProps<T>,
|
||||
state: ListState<T>,
|
||||
ref: RefObject<FocusableElement>,
|
||||
): ActionGroupAria {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
padding-inline: var(--inner-spacing-3);
|
||||
padding-block: var(--inner-spacing-2);
|
||||
min-inline-size: var(--sizing-14);
|
||||
max-inline-size: var(--sizing-70);
|
||||
border-radius: var(--border-radius-elevation-3);
|
||||
|
||||
@each $color in colors {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from "./src";
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { FocusScope } from "@react-aria/focus";
|
||||
import { useDOMRef } from "@react-spectrum/utils";
|
||||
import type { DOMRef } from "@react-types/shared";
|
||||
import { useListState } from "@react-stately/list";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
import type { ButtonGroupItemProps, ButtonGroupProps } from "./types";
|
||||
import { ButtonGroupItem } from "./ButtonGroupItem";
|
||||
import { useButtonGroup } from "./useButtonGroup";
|
||||
|
||||
const _ButtonGroup = <T extends object>(
|
||||
props: ButtonGroupProps<T>,
|
||||
ref: DOMRef<HTMLDivElement>,
|
||||
) => {
|
||||
const {
|
||||
color = "accent",
|
||||
density = "regular",
|
||||
isDisabled,
|
||||
onAction,
|
||||
overflowMode = "collapse",
|
||||
size = "medium",
|
||||
variant = "filled",
|
||||
...others
|
||||
} = props;
|
||||
const domRef = useDOMRef(ref);
|
||||
const state = useListState({ ...props, suppressTextValueWarning: true });
|
||||
const { buttonGroupProps, isMeasuring, orientation } = useButtonGroup(
|
||||
props,
|
||||
state,
|
||||
domRef,
|
||||
);
|
||||
|
||||
const children = [...state.collection];
|
||||
|
||||
const style = {
|
||||
flexBasis: isMeasuring ? "100%" : undefined,
|
||||
display: "flex",
|
||||
};
|
||||
|
||||
return (
|
||||
<FocusScope>
|
||||
<div
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.buttonGroup}
|
||||
data-density={Boolean(density) ? density : undefined}
|
||||
data-orientation={orientation}
|
||||
data-overflow={overflowMode}
|
||||
ref={domRef}
|
||||
{...buttonGroupProps}
|
||||
{...others}
|
||||
>
|
||||
{children.map((item) => {
|
||||
if (Boolean(item.props.isSeparator)) {
|
||||
return <div data-separator="" key={item.key} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonGroupItem
|
||||
color={
|
||||
(item.props.color as ButtonGroupItemProps<object>["color"]) ??
|
||||
color
|
||||
}
|
||||
icon={item.props.icon}
|
||||
iconPosition={item.props.iconPosition}
|
||||
isDisabled={
|
||||
Boolean(state.disabledKeys.has(item.key)) ||
|
||||
Boolean(isDisabled) ||
|
||||
item.props.isDisabled
|
||||
}
|
||||
isLoading={item.props.isLoading}
|
||||
item={item}
|
||||
key={item.key}
|
||||
onPress={() => onAction?.(item.key)}
|
||||
size={Boolean(size) ? size : undefined}
|
||||
state={state}
|
||||
variant={
|
||||
(item.props
|
||||
.variant as ButtonGroupItemProps<object>["variant"]) ??
|
||||
variant
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</FocusScope>
|
||||
);
|
||||
};
|
||||
|
||||
export const ButtonGroup = forwardRef(_ButtonGroup);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { Button } from "@design-system/widgets";
|
||||
|
||||
import type { ButtonGroupItemProps } from "./types";
|
||||
import type { ButtonRef as HeadlessButtonRef } from "@design-system/headless";
|
||||
|
||||
const _ButtonGroupItem = <T extends object>(
|
||||
props: ButtonGroupItemProps<T>,
|
||||
ref: HeadlessButtonRef,
|
||||
) => {
|
||||
const { color, item, variant, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Button color={color} ref={ref} variant={variant} {...rest}>
|
||||
{item.rendered}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const ButtonGroupItem = forwardRef(_ButtonGroupItem);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./types";
|
||||
export { ButtonGroup } from "./ButtonGroup";
|
||||
export { ButtonGroupItem } from "./ButtonGroupItem";
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
@import "../../../shared/colors/colors.module.css";
|
||||
|
||||
.buttonGroup {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: var(--inner-spacing-2);
|
||||
|
||||
& :is([data-button]) {
|
||||
min-inline-size: fit-content;
|
||||
}
|
||||
|
||||
&[data-orientation="vertical"] {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&[data-orientation="vertical"] :is([data-button]) {
|
||||
min-inline-size: 100%;
|
||||
max-inline-size: none;
|
||||
}
|
||||
|
||||
& :is([data-separator]) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ export const ACTION_GROUP_ORIENTATIONS = {
|
|||
export interface InheritedActionButtonProps
|
||||
extends Pick<ButtonProps, "variant" | "color"> {}
|
||||
|
||||
export interface ActionGroupProps<T>
|
||||
export interface ButtonGroupProps<T>
|
||||
extends Omit<
|
||||
SpectrumActionGroupProps<T>,
|
||||
| "staticColor"
|
||||
|
|
@ -36,7 +36,7 @@ export interface ActionGroupProps<T>
|
|||
size?: Omit<keyof typeof SIZES, "large">;
|
||||
}
|
||||
|
||||
export interface ActionGroupItemProps<T> extends ButtonProps {
|
||||
export interface ButtonGroupItemProps<T> extends ButtonProps {
|
||||
state: ListState<T>;
|
||||
item: Node<T>;
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import { createFocusManager } from "@react-aria/focus";
|
||||
import type { ListState } from "@react-stately/list";
|
||||
import { useCallback, type RefObject, useMemo } from "react";
|
||||
import type { DOMAttributes, FocusableElement } from "@react-types/shared";
|
||||
|
||||
import type { ButtonGroupProps } from "../../../";
|
||||
import {
|
||||
useLayoutEffect,
|
||||
useResizeObserver,
|
||||
useValueEffect,
|
||||
} from "@react-aria/utils";
|
||||
|
||||
export interface ButtonGroupAria {
|
||||
buttonGroupProps: DOMAttributes;
|
||||
isMeasuring: boolean;
|
||||
orientation: ButtonGroupProps<object>["orientation"];
|
||||
}
|
||||
|
||||
export function useButtonGroup<T>(
|
||||
props: ButtonGroupProps<T>,
|
||||
state: ListState<T>,
|
||||
ref: RefObject<FocusableElement>,
|
||||
): ButtonGroupAria {
|
||||
const focusManager = createFocusManager(ref);
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<FocusableElement>) => {
|
||||
if (!Boolean(e.currentTarget.contains(e.target as Node))) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowRight":
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
focusManager.focusNext({ wrap: true });
|
||||
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
focusManager.focusPrevious({ wrap: true });
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const [{ isMeasuring, orientation }, setOrientation] = useValueEffect({
|
||||
orientation: props.orientation,
|
||||
isMeasuring: false,
|
||||
});
|
||||
|
||||
const updateOverflow = useCallback(() => {
|
||||
if (props.orientation === "vertical") return;
|
||||
|
||||
const computeOrientation = () => {
|
||||
if (ref.current) {
|
||||
const listItems = Array.from(ref.current.children) as HTMLLIElement[];
|
||||
const containerSize = ref.current.getBoundingClientRect().width;
|
||||
const gap = toNumber(window.getComputedStyle(ref.current).gap);
|
||||
let calculatedSize = 0;
|
||||
|
||||
for (const [i, item] of listItems.entries()) {
|
||||
const itemWidth = item.getBoundingClientRect().width;
|
||||
calculatedSize += itemWidth;
|
||||
|
||||
if (i !== 0) {
|
||||
calculatedSize += gap;
|
||||
}
|
||||
|
||||
if (calculatedSize >= containerSize) {
|
||||
return "vertical";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "horizontal";
|
||||
};
|
||||
|
||||
setOrientation(function* () {
|
||||
yield {
|
||||
orientation: "horizontal",
|
||||
isMeasuring: true,
|
||||
};
|
||||
|
||||
const orientation = computeOrientation();
|
||||
|
||||
yield {
|
||||
orientation: orientation,
|
||||
isMeasuring: true,
|
||||
};
|
||||
|
||||
if (isMeasuring) {
|
||||
yield {
|
||||
orientation: orientation,
|
||||
isMeasuring: false,
|
||||
};
|
||||
}
|
||||
});
|
||||
}, [ref, state.collection, setOrientation, props.orientation]);
|
||||
|
||||
const parentRef = useMemo(
|
||||
() => ({
|
||||
get current() {
|
||||
return ref.current?.parentElement;
|
||||
},
|
||||
}),
|
||||
[ref],
|
||||
);
|
||||
|
||||
useResizeObserver({
|
||||
ref: parentRef,
|
||||
onResize: updateOverflow,
|
||||
});
|
||||
useLayoutEffect(updateOverflow, [updateOverflow, state.collection]);
|
||||
|
||||
return {
|
||||
buttonGroupProps: {
|
||||
"aria-orientation": orientation,
|
||||
onKeyDown,
|
||||
},
|
||||
isMeasuring,
|
||||
orientation,
|
||||
};
|
||||
}
|
||||
|
||||
function toNumber(value: string) {
|
||||
const parsed = parseInt(value, 10);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import {
|
||||
Flex,
|
||||
ButtonGroup,
|
||||
COLORS,
|
||||
Item,
|
||||
BUTTON_VARIANTS,
|
||||
} from "@design-system/widgets";
|
||||
import { Canvas, Meta, Story, ArgsTable, Source } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Design-system/Widgets/Button Group" component={ButtonGroup} />
|
||||
|
||||
# Button Group
|
||||
|
||||
A `ButtonGroup` is a group of buttons that are visually connected together.
|
||||
The `Item` accepts the same props as the `Button` except `variant` and `color`.
|
||||
More information about `Button` props you can find [here](?path=/docs/design-system-widgets-button--docs).
|
||||
|
||||
export const Template = (args) => {
|
||||
return (
|
||||
<ButtonGroup {...args}>
|
||||
<Item>Option 1</Item>
|
||||
<Item>Option 2</Item>
|
||||
<Item>Option 3</Item>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
<Canvas>
|
||||
<Story name="Button Group">{Template.bind({})}</Story>
|
||||
</Canvas>
|
||||
|
||||
<ArgsTable story="Button Group" of={ButtonGroup} />
|
||||
|
||||
## Semantic
|
||||
|
||||
`Button` component has 3 visual style variants and 5 semantic color options
|
||||
|
||||
<Story name="Semantic">
|
||||
<Flex direction="column" gap="spacing-4" alignItems="center" width="100%">
|
||||
{Object.values(BUTTON_VARIANTS).map((variant) =>
|
||||
Object.values(COLORS).map((color) => (
|
||||
<ButtonGroup
|
||||
variant={variant}
|
||||
color={color}
|
||||
key={`${variant}-${color}`}
|
||||
>
|
||||
<Item>
|
||||
{variant} {color}
|
||||
</Item>
|
||||
<Item>
|
||||
{variant} {color}
|
||||
</Item>
|
||||
<Item>
|
||||
{variant} {color}
|
||||
</Item>
|
||||
</ButtonGroup>
|
||||
)),
|
||||
)}
|
||||
</Flex>
|
||||
</Story>
|
||||
|
||||
## Orientation
|
||||
|
||||
<Story name="Orientation horizontal">{Template.bind({})}</Story>
|
||||
|
||||
<Story
|
||||
name="Orientation vertical"
|
||||
args={{
|
||||
orientation: "vertical",
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
|
||||
# Responsive
|
||||
|
||||
<Story name="Responsive">
|
||||
<div style={{ width: 150 }}>
|
||||
<ButtonGroup variant="outlined">
|
||||
<Item key="option-1">Option 1</Item>
|
||||
<Item key="option-2">Option 2</Item>
|
||||
<Item key="option-3" icon="file">
|
||||
{"Option 3"}
|
||||
</Item>
|
||||
<Item key="option-4" icon="trash" iconPosition="end" color="negative">
|
||||
{"Option 4"}
|
||||
</Item>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
## ButtonGroupItem usage
|
||||
|
||||
<Story name="ButtonGroupItem usage">
|
||||
<ButtonGroup>
|
||||
<Item isDisabled>Option 2</Item>
|
||||
<Item isLoading>Option 2</Item>
|
||||
<Item>Option 3</Item>
|
||||
</ButtonGroup>
|
||||
</Story>
|
||||
|
||||
<Source
|
||||
dark
|
||||
code={`
|
||||
<ButtonGroup>
|
||||
<Item>Option 2</Item>
|
||||
<Item isLoading>Option 2</Item>
|
||||
<Item>Option 3</Item>
|
||||
</ButtonGroup>
|
||||
`}
|
||||
/>
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import React from "react";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function FallbackIcon(props: any) {
|
||||
// adding height and width to svg to avoid expanded svg when lazy loading
|
||||
return (
|
||||
<svg
|
||||
// adding styles of icon here as well so that icon takes appropriate space even when the actual icon is not loaded
|
||||
className={styles.icon}
|
||||
data-icon=""
|
||||
data-testid="t--fallback-icon"
|
||||
height={0}
|
||||
width={0}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from "react";
|
||||
import React, { Suspense, forwardRef, lazy } from "react";
|
||||
import React, { Suspense, forwardRef, lazy, useMemo } from "react";
|
||||
import { useThemeContext } from "@design-system/theming";
|
||||
import { Icon as HeadlessIcon } from "@design-system/headless";
|
||||
|
||||
|
|
@ -13,40 +13,51 @@ const _Icon = (props: IconProps, ref: Ref<SVGSVGElement>) => {
|
|||
const theme = useThemeContext();
|
||||
const filled = theme.iconStyle === "filled" || filledProp;
|
||||
|
||||
let Icon: React.ComponentType | null = null;
|
||||
const Icon = useMemo(() => {
|
||||
let Icon: React.ComponentType | null = null;
|
||||
|
||||
if (icon !== undefined) {
|
||||
Icon = icon as React.ComponentType;
|
||||
} else if (name !== undefined) {
|
||||
const pascalName = ICONS[name];
|
||||
if (icon !== undefined) {
|
||||
Icon = icon as React.ComponentType;
|
||||
} else if (name !== undefined) {
|
||||
const pascalName = ICONS[name];
|
||||
|
||||
Icon = lazy(async () =>
|
||||
import("@tabler/icons-react").then((module) => {
|
||||
if (Boolean(filled)) {
|
||||
const filledVariant = `${pascalName}Filled`;
|
||||
Icon = lazy(async () =>
|
||||
import("@tabler/icons-react").then((module) => {
|
||||
if (Boolean(filled)) {
|
||||
const filledVariant = `${pascalName}Filled`;
|
||||
|
||||
if (filledVariant in module) {
|
||||
if (filledVariant in module) {
|
||||
return {
|
||||
default: module[filledVariant] as React.ComponentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (pascalName in module) {
|
||||
return {
|
||||
default: module[filledVariant] as React.ComponentType,
|
||||
default: module[pascalName] as React.ComponentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (pascalName in module) {
|
||||
return {
|
||||
default: module[pascalName] as React.ComponentType,
|
||||
};
|
||||
}
|
||||
|
||||
return { default: FallbackIcon };
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
Icon = FallbackIcon;
|
||||
}
|
||||
return { default: FallbackIcon };
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
Icon = FallbackIcon;
|
||||
}
|
||||
return Icon;
|
||||
}, [name, icon, filled]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<FallbackIcon {...rest} />}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<FallbackIcon
|
||||
className={styles.icon}
|
||||
data-size={Boolean(size) ? size : undefined}
|
||||
{...rest}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeadlessIcon
|
||||
className={styles.icon}
|
||||
data-size={Boolean(size) ? size : undefined}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import type { ItemProps as HeadlessItemProps } from "@react-types/shared";
|
|||
|
||||
import type { IconProps } from "../../Icon";
|
||||
import type { COLORS } from "../../../shared";
|
||||
import type { ButtonProps } from "../../Button";
|
||||
|
||||
interface ItemProps<T> extends HeadlessItemProps<T> {
|
||||
color?: keyof typeof COLORS;
|
||||
variant?: ButtonProps["variant"];
|
||||
icon?: IconProps["name"];
|
||||
iconPosition?: "start" | "end";
|
||||
isLoading?: boolean;
|
||||
|
|
@ -24,6 +26,8 @@ _Item.getCollectionNode = <T,>(props: ItemProps<T>) => {
|
|||
// @ts-expect-error this method is hidden by the types. See the source code of Item from Spectrum for more context.
|
||||
return HeadlessItem.getCollectionNode({
|
||||
...rest,
|
||||
color,
|
||||
// TODO(pawan): Check why we need [data-color] here.
|
||||
["data-color"]: Boolean(color) ? color : undefined,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Text } from "../../Text";
|
|||
import { IconButton } from "../../IconButton";
|
||||
import { Flex } from "../../Flex";
|
||||
import { useId } from "@floating-ui/react";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
import type { ModalHeaderProps } from "./types";
|
||||
|
||||
|
|
@ -21,8 +22,19 @@ export const ModalHeader = (props: ModalHeaderProps) => {
|
|||
}, [id, setLabelId]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" gap="spacing-4" justifyContent="space-between">
|
||||
<Text id={id} lineClamp={1} title={title} variant="caption">
|
||||
<Flex
|
||||
alignItems="center"
|
||||
className={styles.header}
|
||||
gap="spacing-4"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text
|
||||
fontWeight={600}
|
||||
id={id}
|
||||
lineClamp={1}
|
||||
title={title}
|
||||
variant="subtitle"
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<IconButton icon="x" onPress={() => setOpen(false)} variant="ghost" />
|
||||
|
|
|
|||
|
|
@ -14,22 +14,27 @@
|
|||
outline: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--outer-spacing-4);
|
||||
padding-inline: var(--outer-spacing-4);
|
||||
padding-block: var(--outer-spacing-4);
|
||||
gap: var(--inner-spacing-3);
|
||||
padding-inline: var(--inner-spacing-4);
|
||||
padding-block: var(--inner-spacing-3);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.overlay .content .header {
|
||||
padding-inline: 0 calc(var(--inner-spacing-2));
|
||||
margin-inline: 0 calc(-1 * var(--inner-spacing-4));
|
||||
}
|
||||
|
||||
.overlay .content .body {
|
||||
overflow: auto;
|
||||
padding-inline: var(--outer-spacing-4);
|
||||
margin-inline: calc(var(--outer-spacing-4) * -1);
|
||||
padding-inline: var(--inner-spacing-4);
|
||||
margin-inline: calc(var(--inner-spacing-4) * -1);
|
||||
}
|
||||
|
||||
.overlay [role="dialog"] {
|
||||
display: flex;
|
||||
max-inline-size: calc(100% - var(--outer-spacing-8));
|
||||
max-block-size: calc(100% - var(--outer-spacing-8));
|
||||
max-inline-size: calc(100% - var(--inner-spacing-8));
|
||||
max-block-size: calc(100% - var(--inner-spacing-8));
|
||||
}
|
||||
|
||||
.overlay [role="dialog"][data-size="small"] {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export * from "./components/Menu";
|
|||
export * from "./components/Modal";
|
||||
export * from "./components/TagGroup";
|
||||
export * from "./components/ActionGroup";
|
||||
export * from "./components/ButtonGroup";
|
||||
|
||||
export * from "./utils";
|
||||
export * from "./styles";
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ export interface UpdateActionNameRequest {
|
|||
newName: string;
|
||||
oldName: string;
|
||||
moduleId?: string;
|
||||
workflowId?: string;
|
||||
contextType?: ActionParentEntityTypeInterface;
|
||||
}
|
||||
|
||||
|
|
|
|||
15
app/client/src/ce/actions/auditLogsAction.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Code splitting helper for audit logs
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const logActionExecutionForAudit = (payload: {
|
||||
actionId: string;
|
||||
pageId: string;
|
||||
collectionId: string;
|
||||
actionName: string;
|
||||
pageName: string;
|
||||
}) => {
|
||||
return {
|
||||
type: "",
|
||||
};
|
||||
};
|
||||
|
|
@ -23,6 +23,7 @@ export interface UpdateJSObjectNameRequest {
|
|||
newName: string;
|
||||
oldName: string;
|
||||
moduleId?: string;
|
||||
workflowId?: string;
|
||||
contextType?: ActionParentEntityTypeInterface;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const generateDataTreeJSAction = (
|
|||
for (let i = 0; i < actions.length; i++) {
|
||||
const action = actions[i];
|
||||
meta[action.name] = {
|
||||
arguments: action.actionConfiguration.jsArguments,
|
||||
arguments: action.actionConfiguration?.jsArguments,
|
||||
confirmBeforeExecute: !!action.confirmBeforeExecute,
|
||||
};
|
||||
bindingPaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE;
|
||||
|
|
|
|||
|
|
@ -55,3 +55,5 @@ export const doesPluginRequireDatasource = (plugin: Plugin | undefined) => {
|
|||
? plugin.requiresDatasource
|
||||
: true;
|
||||
};
|
||||
|
||||
export enum APPSMITH_NAMESPACED_FUNCTIONS {}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ export const FEATURE_FLAG = {
|
|||
release_side_by_side_ide_enabled: "release_side_by_side_ide_enabled",
|
||||
release_global_add_pane_enabled: "release_global_add_pane_enabled",
|
||||
ab_appsmith_ai_query: "ab_appsmith_ai_query",
|
||||
ab_flip_primary_secondary_ctas_dsform_enabled:
|
||||
"ab_flip_primary_secondary_ctas_dsform_enabled",
|
||||
rollout_consolidated_page_load_fetch_enabled:
|
||||
"rollout_consolidated_page_load_fetch_enabled",
|
||||
ab_start_with_data_default_enabled: "ab_start_with_data_default_enabled",
|
||||
|
|
@ -90,7 +88,6 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
|
|||
release_side_by_side_ide_enabled: false,
|
||||
release_global_add_pane_enabled: false,
|
||||
ab_appsmith_ai_query: false,
|
||||
ab_flip_primary_secondary_ctas_dsform_enabled: false,
|
||||
rollout_consolidated_page_load_fetch_enabled: false,
|
||||
ab_start_with_data_default_enabled: false,
|
||||
release_actions_redesign_enabled: false,
|
||||
|
|
|
|||
|
|
@ -97,7 +97,10 @@ export const handlers = {
|
|||
isMainJSCollection: true,
|
||||
displayName: "Main",
|
||||
}
|
||||
: action.payload.data,
|
||||
: {
|
||||
...action.payload.data,
|
||||
isMainJSCollection: jsCollection.config.isMainJSCollection,
|
||||
},
|
||||
activeJSActionId:
|
||||
findIndex(jsCollection.config.actions, {
|
||||
id: jsCollection.activeJSActionId,
|
||||
|
|
@ -123,7 +126,10 @@ export const handlers = {
|
|||
isMainJSCollection: true,
|
||||
displayName: "Main",
|
||||
}
|
||||
: action.payload.data,
|
||||
: {
|
||||
...action.payload.data,
|
||||
isMainJSCollection: a.config.isMainJSCollection,
|
||||
},
|
||||
};
|
||||
return a;
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ import {
|
|||
} from "pages/Editor/Explorer/ExplorerIcons";
|
||||
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
|
||||
|
||||
export enum GROUP_TYPES {
|
||||
API = "APIs",
|
||||
JS_ACTIONS = "JS Objects",
|
||||
AI = "AI Queries",
|
||||
WORKFLOWS = "Workflows",
|
||||
PACKAGES = "Packages",
|
||||
}
|
||||
|
||||
export const getEntities = (state: AppState): AppState["entities"] =>
|
||||
state.entities;
|
||||
|
||||
|
|
@ -1042,20 +1050,20 @@ export const selectFilesForExplorer = createSelector(
|
|||
...workflowActions,
|
||||
...workflowJsActions,
|
||||
].reduce((acc, file) => {
|
||||
let group = "";
|
||||
let group;
|
||||
if (file.config.pluginType === PluginType.JS) {
|
||||
group = "JS Objects";
|
||||
group = GROUP_TYPES.JS_ACTIONS;
|
||||
} else if (file.config.pluginType === PluginType.API) {
|
||||
group = isEmbeddedRestDatasource(file.config.datasource)
|
||||
? "APIs"
|
||||
: datasourceIdToNameMap[file.config.datasource.id] ?? "APIs";
|
||||
? GROUP_TYPES.API
|
||||
: datasourceIdToNameMap[file.config.datasource.id] ?? GROUP_TYPES.API;
|
||||
} else if (file.config.pluginType === PluginType.AI) {
|
||||
group = isEmbeddedAIDataSource(file.config.datasource)
|
||||
? "AI Queries"
|
||||
: datasourceIdToNameMap[file.config.datasource.id] ?? "AI Queries";
|
||||
? GROUP_TYPES.AI
|
||||
: datasourceIdToNameMap[file.config.datasource.id] ?? GROUP_TYPES.AI;
|
||||
} else if (file.config.pluginType === PluginType.INTERNAL) {
|
||||
// TODO: Add a group for internal actions, currently only Workflow actions are internal
|
||||
group = "Workflows";
|
||||
group = GROUP_TYPES.WORKFLOWS;
|
||||
} else {
|
||||
group = datasourceIdToNameMap[file.config.datasource.id];
|
||||
}
|
||||
|
|
|
|||
7
app/client/src/ce/utils/analyticsHelpers.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
export const logMainJsActionExecution = (
|
||||
actionId: string,
|
||||
isSuccess: boolean,
|
||||
collectionId: string,
|
||||
isDirty: boolean,
|
||||
) => {};
|
||||
|
|
@ -15,6 +15,7 @@ import type {
|
|||
} from "@appsmith/entities/DataTree/types";
|
||||
import type { FieldEntityInformation } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
|
||||
import { eeAppsmithAutocompleteDefs } from "@appsmith/utils/autocomplete/helpers";
|
||||
|
||||
export const entityDefinitions = {
|
||||
APPSMITH: (entity: AppsmithEntity, extraDefsToDefine: ExtraDef) => {
|
||||
|
|
@ -88,6 +89,7 @@ export const entityDefinitions = {
|
|||
"https://docs.appsmith.com/reference/appsmith-framework/context-object#geolocationclearwatch",
|
||||
},
|
||||
},
|
||||
...eeAppsmithAutocompleteDefs(generatedTypeDef),
|
||||
};
|
||||
}
|
||||
return generatedTypeDef;
|
||||
|
|
|
|||
5
app/client/src/ce/utils/autocomplete/helpers.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { Def } from "tern";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const eeAppsmithAutocompleteDefs = (generatedTypeDef: Def) => ({});
|
||||
|
|
@ -11,7 +11,7 @@ import type { EvalContext } from "workers/Evaluation/evaluate";
|
|||
import type { EvaluationVersion } from "@appsmith/api/ApplicationApi";
|
||||
import { addFn } from "workers/Evaluation/fns/utils/fnGuard";
|
||||
import {
|
||||
entityFns,
|
||||
getEntityFunctions,
|
||||
getPlatformFunctions,
|
||||
} from "@appsmith/workers/Evaluation/fns";
|
||||
import { getEntityForEvalContext } from "workers/Evaluation/getEntityForContext";
|
||||
|
|
@ -67,7 +67,7 @@ export const addDataTreeToContext = (args: {
|
|||
|
||||
if (skipEntityFunctions) continue;
|
||||
|
||||
for (const entityFn of entityFns) {
|
||||
for (const entityFn of getEntityFunctions()) {
|
||||
if (!entityFn.qualifier(entity)) continue;
|
||||
const func = entityFn.fn(entity, entityName);
|
||||
const fullPath = `${entityFn.path || `${entityName}.${entityFn.name}`}`;
|
||||
|
|
@ -166,7 +166,7 @@ export const getAllAsyncFunctions = (
|
|||
let allAsyncFunctions: Record<string, true> = {};
|
||||
const dataTreeEntries = Object.entries(dataTree);
|
||||
for (const [entityName, entity] of dataTreeEntries) {
|
||||
for (const entityFn of entityFns) {
|
||||
for (const entityFn of getEntityFunctions()) {
|
||||
if (!entityFn.qualifier(entity)) continue;
|
||||
const fullPath = `${entityFn.path || `${entityName}.${entityFn.name}`}`;
|
||||
allAsyncFunctions[fullPath] = true;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { APPSMITH_NAMESPACED_FUNCTIONS as EE_APPSMITH_NAMESPACED_FUNCTIONS } from "@appsmith/entities/Engine/actionHelpers";
|
||||
|
||||
export enum APPSMITH_GLOBAL_FUNCTIONS {
|
||||
navigateTo = "navigateTo",
|
||||
showAlert = "showAlert",
|
||||
|
|
@ -30,6 +32,7 @@ export const AppsmithFunction = {
|
|||
...APPSMITH_INTEGRATIONS,
|
||||
...APPSMITH_GLOBAL_FUNCTIONS,
|
||||
...APPSMITH_NAMESPACED_FUNCTIONS,
|
||||
...EE_APPSMITH_NAMESPACED_FUNCTIONS,
|
||||
};
|
||||
|
||||
export const AppsmithFunctionsWithFields = [
|
||||
|
|
|
|||
|
|
@ -461,6 +461,7 @@ function getApiAndQueryOptions(
|
|||
action.config.pluginType === PluginType.API ||
|
||||
action.config.pluginType === PluginType.SAAS ||
|
||||
action.config.pluginType === PluginType.REMOTE ||
|
||||
action.config.pluginType === PluginType.INTERNAL ||
|
||||
action.config.pluginType === PluginType.AI,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ function GetIconForAction(
|
|||
|
||||
case AppsmithFunction.postWindowMessage:
|
||||
return () => <Icon name="chat-upload-icon" />;
|
||||
|
||||
default:
|
||||
return () => <Icon name="js" />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export const useActions = () => {
|
|||
(action) =>
|
||||
action.config.pluginType === PluginType.SAAS ||
|
||||
action.config.pluginType === PluginType.REMOTE ||
|
||||
action.config.pluginType === PluginType.INTERNAL ||
|
||||
action.config.pluginType === PluginType.AI,
|
||||
)
|
||||
.map((action) => action.config);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,17 @@ interface State {
|
|||
focusedIndex: number | null;
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
id: string;
|
||||
label: string;
|
||||
isDisabled: boolean;
|
||||
isVisible: boolean;
|
||||
widgetId: string;
|
||||
itemType: "SEPARATOR" | "BUTTON";
|
||||
}
|
||||
|
||||
class ButtonListControl extends BaseControl<
|
||||
ControlProps & { allowSeparators?: boolean },
|
||||
ControlProps & { allowSeparators?: boolean; allowSpatialGrouping?: boolean },
|
||||
State
|
||||
> {
|
||||
constructor(props: ControlProps) {
|
||||
|
|
@ -38,14 +47,7 @@ class ButtonListControl extends BaseControl<
|
|||
}
|
||||
|
||||
getMenuItems = () => {
|
||||
const menuItems: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
isDisabled: boolean;
|
||||
isVisible: boolean;
|
||||
widgetId: string;
|
||||
isSeparator: boolean;
|
||||
}> =
|
||||
const menuItems: MenuItem[] =
|
||||
isString(this.props.propertyValue) ||
|
||||
isUndefined(this.props.propertyValue)
|
||||
? []
|
||||
|
|
@ -68,6 +70,7 @@ class ButtonListControl extends BaseControl<
|
|||
onEdit = (index: number) => {
|
||||
const menuItems = this.getMenuItems();
|
||||
const targetMenuItem = menuItems[index];
|
||||
|
||||
this.props.openNextPanel({
|
||||
index,
|
||||
...targetMenuItem,
|
||||
|
|
@ -76,6 +79,10 @@ class ButtonListControl extends BaseControl<
|
|||
};
|
||||
|
||||
render() {
|
||||
const hasSeparator = this.getMenuItems().some(
|
||||
(item: MenuItem) => item.itemType === "SEPARATOR",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<DraggableListControl
|
||||
|
|
@ -99,8 +106,9 @@ class ButtonListControl extends BaseControl<
|
|||
updateOption={this.updateOption}
|
||||
/>
|
||||
|
||||
<Flex justifyContent="end">
|
||||
{this.props.allowSeparators && (
|
||||
<Flex gap="spaces-3" justifyContent="end">
|
||||
{(this.props.allowSeparators ||
|
||||
(this.props.allowSpatialGrouping && !hasSeparator)) && (
|
||||
<Button
|
||||
className="self-end"
|
||||
kind="tertiary"
|
||||
|
|
@ -185,13 +193,14 @@ class ButtonListControl extends BaseControl<
|
|||
isDisabled: false,
|
||||
itemType: isSeparator ? "SEPARATOR" : "BUTTON",
|
||||
isVisible: true,
|
||||
buttonVariant: "filled",
|
||||
},
|
||||
};
|
||||
|
||||
if (this.props.widgetProperties.type === "BUTTON_GROUP_WIDGET") {
|
||||
/**
|
||||
/**c
|
||||
* These properties are required for "BUTTON_GROUP_WIDGET" but not for
|
||||
* "WDS_BUTTON_GROUP_WIDGET"
|
||||
* "WDS_TOOLBAR_BUTTONS_GROUP_WIDGET"
|
||||
*/
|
||||
const optionalButtonGroupItemProperties = {
|
||||
menuItems: {},
|
||||
|
|
@ -207,6 +216,21 @@ class ButtonListControl extends BaseControl<
|
|||
};
|
||||
}
|
||||
|
||||
// if the widget is a WDS_INLINE_BUTTONS_WIDGET, and button already have filled button variant in groupButtons,
|
||||
// then we should add a secondary button ( outlined button ) instead of simple button
|
||||
if (this.props.widgetProperties.type === "WDS_INLINE_BUTTONS_WIDGET") {
|
||||
const filledButtonVariant = groupButtonsArray.find(
|
||||
(groupButton: any) => groupButton.buttonVariant === "filled",
|
||||
);
|
||||
|
||||
if (filledButtonVariant) {
|
||||
groupButtons[newGroupButtonId] = {
|
||||
...groupButtons[newGroupButtonId],
|
||||
buttonVariant: "outlined",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.updateProperty(this.props.propertyName, groupButtons);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export function DraggableListCard(props: RenderComponentProps) {
|
|||
startIcon="settings-2-line"
|
||||
/>
|
||||
)}
|
||||
{showDelete && (
|
||||
{showDelete && !isSeparator && (
|
||||
<Button
|
||||
className="t--delete-column-btn"
|
||||
isIconButton
|
||||
|
|
@ -216,7 +216,10 @@ export function DraggableListCard(props: RenderComponentProps) {
|
|||
startIcon="delete-bin-line"
|
||||
/>
|
||||
)}
|
||||
{!showDelete && toggleVisibility && renderVisibilityIcon()}
|
||||
{!showDelete &&
|
||||
!isSeparator &&
|
||||
toggleVisibility &&
|
||||
renderVisibilityIcon()}
|
||||
{/*
|
||||
* Used in Table_Widget_V2's primary columns to enable/disable cell editability.
|
||||
* Using a common name `showCheckbox` instead of showEditable or isEditable,
|
||||
|
|
|
|||
1
app/client/src/ee/actions/auditLogsAction.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/actions/auditLogsAction";
|
||||
1
app/client/src/ee/utils/analyticsHelpers.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/utils/analyticsHelpers";
|
||||
1
app/client/src/ee/utils/autocomplete/helpers.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/utils/autocomplete/helpers";
|
||||
|
|
@ -167,10 +167,6 @@ function DatasourceAuth({
|
|||
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
|
||||
const isEnabledForTestPrimary = !!useFeatureFlag(
|
||||
FEATURE_FLAG.ab_flip_primary_secondary_ctas_dsform_enabled,
|
||||
);
|
||||
|
||||
const canManageDatasource = getHasManageDatasourcePermission(
|
||||
isFeatureEnabled,
|
||||
datasourcePermissions,
|
||||
|
|
@ -336,11 +332,7 @@ function DatasourceAuth({
|
|||
floatLeft={!isInsideReconnectModal}
|
||||
isLoading={isTesting}
|
||||
key={buttonType}
|
||||
kind={
|
||||
isEnabledForTestPrimary && pluginType === "DB"
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
kind="secondary"
|
||||
onClick={handleDatasourceTest}
|
||||
size="md"
|
||||
>
|
||||
|
|
@ -387,11 +379,6 @@ function DatasourceAuth({
|
|||
}
|
||||
isLoading={isSaving}
|
||||
key={buttonType}
|
||||
kind={
|
||||
isEnabledForTestPrimary && pluginType === "DB"
|
||||
? "secondary"
|
||||
: "primary"
|
||||
}
|
||||
onClick={
|
||||
authType === AuthType.OAUTH2
|
||||
? handleOauthDatasourceSave
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
LintEntityTree,
|
||||
type EntityTree,
|
||||
} from "plugins/Linting/lib/entity/EntityTree";
|
||||
import { entityFns } from "workers/Evaluation/fns";
|
||||
import { getEntityFunctions } from "@appsmith/workers/Evaluation/fns";
|
||||
|
||||
class LintService {
|
||||
cachedEntityTree: EntityTree | null;
|
||||
|
|
@ -339,7 +339,7 @@ function filterDataPaths(paths: string[], entityTree: EntityTree) {
|
|||
function getAllEntityActions(entityTree: EntityTree) {
|
||||
const allEntityActions = new Set<string>();
|
||||
for (const [entityName, entity] of Object.entries(entityTree.getRawTree())) {
|
||||
for (const entityFnDescription of entityFns) {
|
||||
for (const entityFnDescription of getEntityFunctions()) {
|
||||
if (entityFnDescription.qualifier(entity)) {
|
||||
const fullPath = `${
|
||||
entityFnDescription.path ||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DataTreeEntity } from "entities/DataTree/dataTreeTypes";
|
||||
import { isDataTreeEntity } from "@appsmith/workers/Evaluation/evaluationUtils";
|
||||
import { entityFns } from "@appsmith/workers/Evaluation/fns";
|
||||
import { getEntityFunctions } from "@appsmith/workers/Evaluation/fns";
|
||||
import setters from "workers/Evaluation/setters";
|
||||
|
||||
export default function isEntityFunction(
|
||||
|
|
@ -12,6 +12,8 @@ export default function isEntityFunction(
|
|||
|
||||
if (setters.has(entityName, propertyName)) return true;
|
||||
|
||||
const entityFns = getEntityFunctions();
|
||||
|
||||
return entityFns.find((entityFn) => {
|
||||
const entityFnpropertyName = entityFn.path
|
||||
? entityFn.path.split(".")[1]
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export interface AppDataState {
|
|||
canBeRequested: boolean;
|
||||
currentPosition?: Partial<GeolocationPosition>;
|
||||
};
|
||||
workflows: Record<string, any>;
|
||||
}
|
||||
|
||||
const initialState: AppDataState = {
|
||||
|
|
@ -55,6 +56,7 @@ const initialState: AppDataState = {
|
|||
canBeRequested: "geolocation" in navigator,
|
||||
currentPosition: {},
|
||||
},
|
||||
workflows: {},
|
||||
};
|
||||
|
||||
const appReducer = createReducer(initialState, {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
getCurrentPageName,
|
||||
getIsSavingEntity,
|
||||
} from "selectors/editorSelectors";
|
||||
import {
|
||||
|
|
@ -99,6 +100,8 @@ import {
|
|||
getJSActionPathNameToDisplay,
|
||||
} from "@appsmith/utils/actionExecutionUtils";
|
||||
import { getJsPaneDebuggerState } from "selectors/jsPaneSelectors";
|
||||
import { logMainJsActionExecution } from "@appsmith/utils/analyticsHelpers";
|
||||
import { logActionExecutionForAudit } from "@appsmith/actions/auditLogsAction";
|
||||
|
||||
export interface GenerateDefaultJSObjectProps {
|
||||
name: string;
|
||||
|
|
@ -455,7 +458,21 @@ export function* handleExecuteJSFunctionSaga(data: {
|
|||
},
|
||||
});
|
||||
|
||||
if (!!collection.isMainJSCollection)
|
||||
logMainJsActionExecution(actionId, true, collectionId, isDirty);
|
||||
|
||||
yield put(
|
||||
logActionExecutionForAudit({
|
||||
actionName: action.name,
|
||||
actionId: action.id,
|
||||
collectionId: collectionId,
|
||||
pageId: action.pageId,
|
||||
pageName: yield select(getCurrentPageName),
|
||||
}),
|
||||
);
|
||||
|
||||
const jsActionNameToDisplay = getJSActionNameToDisplay(action);
|
||||
|
||||
AppsmithConsole.info({
|
||||
text: createMessage(JS_EXECUTION_SUCCESS),
|
||||
source: {
|
||||
|
|
@ -489,6 +506,10 @@ export function* handleExecuteJSFunctionSaga(data: {
|
|||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (!!collection.isMainJSCollection)
|
||||
logMainJsActionExecution(actionId, false, collectionId, false);
|
||||
|
||||
AppsmithConsole.addErrors([
|
||||
{
|
||||
payload: {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,30 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
const parentContextTypeTokens = ["pkg", "workflow"];
|
||||
|
||||
/**
|
||||
* Function to check the current URL and return the parent context.
|
||||
* For app, function was returning app name due to the way app urls are structured
|
||||
* So this function will only return the parent context for pkg and workflow
|
||||
* @param location current location object based on URL
|
||||
* @returns object {id, type} where type is either pkg or workflow and id is the id of the pkg or workflow
|
||||
*/
|
||||
function getParentContextFromURL(location: Location) {
|
||||
const pathSplit = location.pathname.split("/");
|
||||
let type = parentContextTypeTokens[0];
|
||||
const editorIndex = pathSplit.findIndex((path) =>
|
||||
parentContextTypeTokens.includes(path),
|
||||
);
|
||||
if (editorIndex !== -1) {
|
||||
type = pathSplit[editorIndex];
|
||||
|
||||
const id = pathSplit[editorIndex + 1];
|
||||
|
||||
return { id, type };
|
||||
}
|
||||
}
|
||||
|
||||
function getApplicationId(location: Location) {
|
||||
const pathSplit = location.pathname.split("/");
|
||||
const applicationsIndex = pathSplit.findIndex(
|
||||
|
|
@ -141,6 +165,7 @@ class AnalyticsUtil {
|
|||
const windowDoc: any = window;
|
||||
let finalEventData = eventData;
|
||||
const userData = AnalyticsUtil.user;
|
||||
const parentContext = getParentContextFromURL(windowDoc.location);
|
||||
const instanceId = AnalyticsUtil.instanceId;
|
||||
const appId = getApplicationId(windowDoc.location);
|
||||
const { appVersion, segment } = getAppsmithConfigs();
|
||||
|
|
@ -150,7 +175,7 @@ class AnalyticsUtil {
|
|||
user = {
|
||||
userId: userData.username,
|
||||
email: userData.email,
|
||||
appId: appId,
|
||||
appId,
|
||||
source: "cloud",
|
||||
};
|
||||
} else {
|
||||
|
|
@ -169,7 +194,12 @@ class AnalyticsUtil {
|
|||
userData: user.userId === ANONYMOUS_USERNAME ? undefined : user,
|
||||
};
|
||||
}
|
||||
finalEventData = { ...finalEventData, instanceId, version: appVersion.id };
|
||||
finalEventData = {
|
||||
...finalEventData,
|
||||
instanceId,
|
||||
version: appVersion.id,
|
||||
...(parentContext ? { parentContext } : {}),
|
||||
};
|
||||
|
||||
if (windowDoc.analytics) {
|
||||
log.debug("Event fired", eventName, finalEventData);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ import type BaseWidget from "./BaseWidget";
|
|||
import ExternalWidget from "./ExternalWidget";
|
||||
import { WDSTableWidget } from "./wds/WDSTableWidget";
|
||||
import { WDSCurrencyInputWidget } from "./wds/WDSCurrencyInputWidget";
|
||||
import { WDSButtonGroupWidget } from "./wds/WDSButtonGroupWidget";
|
||||
import { WDSToolbarButtonsWidget } from "./wds/WDSToolbarButtonsWidget";
|
||||
import { WDSPhoneInputWidget } from "./wds/WDSPhoneInputWidget";
|
||||
import { WDSCheckboxGroupWidget } from "./wds/WDSCheckboxGroupWidget";
|
||||
import { WDSSwitchWidget } from "./wds/WDSSwitchWidget";
|
||||
|
|
@ -80,6 +80,7 @@ import { WDSParagraphWidget } from "./wds/WDSParagraphWidget";
|
|||
import { WDSModalWidget } from "./wds/WDSModalWidget";
|
||||
import { WDSStatBoxWidget } from "./wds/WDSStatBoxWidget";
|
||||
import { WDSKeyValueWidget } from "./wds/WDSKeyValueWidget";
|
||||
import { WDSInlineButtonsWidget } from "./wds/WDSInlineButtonsWidget";
|
||||
|
||||
const LegacyWidgets = [
|
||||
CanvasWidget,
|
||||
|
|
@ -156,7 +157,7 @@ const WDSWidgets = [
|
|||
WDSIconButtonWidget,
|
||||
WDSTableWidget,
|
||||
WDSCurrencyInputWidget,
|
||||
WDSButtonGroupWidget,
|
||||
WDSToolbarButtonsWidget,
|
||||
WDSPhoneInputWidget,
|
||||
WDSCheckboxGroupWidget,
|
||||
WDSSwitchWidget,
|
||||
|
|
@ -171,6 +172,7 @@ const WDSWidgets = [
|
|||
WDSModalWidget,
|
||||
WDSStatBoxWidget,
|
||||
WDSKeyValueWidget,
|
||||
WDSInlineButtonsWidget,
|
||||
];
|
||||
|
||||
const Widgets = [
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
import { WDSButtonGroupWidget } from "./widget";
|
||||
|
||||
export { WDSButtonGroupWidget };
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import type { ActionGroupProps } from "@design-system/widgets";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
import type { ButtonGroupItemComponentProps } from "../component/types";
|
||||
|
||||
export type ButtonsList = Record<string, ButtonGroupItemComponentProps>;
|
||||
|
||||
export interface ButtonGroupWidgetProps extends WidgetProps {
|
||||
buttonColor: ActionGroupProps<object>["color"];
|
||||
buttonVariant: ActionGroupProps<object>["variant"];
|
||||
orientation: ActionGroupProps<object>["orientation"];
|
||||
isVisible: boolean;
|
||||
buttonsList: ButtonsList;
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import type { Key } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { ButtonGroup, Item } from "@design-system/widgets";
|
||||
import type {
|
||||
InlineButtonsComponentProps,
|
||||
InlineButtonsItemComponentProps,
|
||||
} from "./types";
|
||||
import { sortBy } from "lodash";
|
||||
|
||||
export const InlineButtonsComponent = (props: InlineButtonsComponentProps) => {
|
||||
const [loadingButtonIds, setLoadingButtonIds] = useState<
|
||||
Array<InlineButtonsItemComponentProps["id"]>
|
||||
>([]);
|
||||
|
||||
const sortedButtons = sortBy(
|
||||
Object.keys(props.buttonsList)
|
||||
.map((key) => props.buttonsList[key])
|
||||
.filter((button) => {
|
||||
return button.isVisible === true;
|
||||
}),
|
||||
["index"],
|
||||
);
|
||||
|
||||
const disabledKeys = Object.keys(props.buttonsList)
|
||||
.map((key) => props.buttonsList[key])
|
||||
.filter((button) => {
|
||||
return button.isDisabled === true;
|
||||
})
|
||||
.map((button) => button.id);
|
||||
|
||||
const onActionComplete = (button: InlineButtonsItemComponentProps) => {
|
||||
const newLoadingButtonIds = [...loadingButtonIds];
|
||||
const index = newLoadingButtonIds.indexOf(button.id);
|
||||
|
||||
if (index > -1) {
|
||||
newLoadingButtonIds.splice(index, 1);
|
||||
}
|
||||
|
||||
setLoadingButtonIds(newLoadingButtonIds);
|
||||
};
|
||||
|
||||
const onAction = (key: Key) => {
|
||||
if (props.buttonsList[key].onClick) {
|
||||
setLoadingButtonIds([...loadingButtonIds, key as string]);
|
||||
|
||||
props.onButtonClick(props.buttonsList[key].onClick, () =>
|
||||
onActionComplete(props.buttonsList[key]),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
color={props.color}
|
||||
density={props.density}
|
||||
disabledKeys={disabledKeys}
|
||||
onAction={onAction}
|
||||
orientation={props.orientation}
|
||||
variant={props.variant}
|
||||
>
|
||||
{sortedButtons.map((button: InlineButtonsItemComponentProps) => {
|
||||
return (
|
||||
<Item
|
||||
color={button.buttonColor}
|
||||
icon={button.iconName}
|
||||
iconPosition={button.iconAlign}
|
||||
isLoading={loadingButtonIds.includes(button.id)}
|
||||
isSeparator={button.itemType === "SEPARATOR"}
|
||||
key={button.id}
|
||||
variant={button.buttonVariant}
|
||||
>
|
||||
{button.label}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import type {
|
||||
ButtonGroupProps,
|
||||
ButtonProps,
|
||||
IconProps,
|
||||
} from "@design-system/widgets";
|
||||
import type { ButtonsList } from "../widget/types";
|
||||
import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
|
||||
export interface InlineButtonsComponentProps {
|
||||
color?: ButtonGroupProps<object>["color"];
|
||||
variant?: ButtonGroupProps<object>["variant"];
|
||||
orientation: ButtonGroupProps<object>["orientation"];
|
||||
buttonsList: ButtonsList;
|
||||
onButtonClick: (
|
||||
onClick?: string,
|
||||
callback?: (result: ExecutionResult) => void,
|
||||
) => void;
|
||||
density?: ButtonGroupProps<object>["density"];
|
||||
}
|
||||
|
||||
export interface InlineButtonsItemComponentProps {
|
||||
label?: string;
|
||||
isVisible?: boolean;
|
||||
isLoading?: boolean;
|
||||
isDisabled?: boolean;
|
||||
widgetId: string;
|
||||
id: string;
|
||||
index: number;
|
||||
iconName?: IconProps["name"];
|
||||
iconAlign?: ButtonProps["iconPosition"];
|
||||
onClick?: string;
|
||||
itemType: "SEPARATOR" | "BUTTON";
|
||||
buttonVariant?: ButtonProps["variant"];
|
||||
buttonColor?: ButtonProps["color"];
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
||||
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
||||
|
||||
export const defaultsConfig = {
|
||||
widgetName: "InlineButtons",
|
||||
isDisabled: false,
|
||||
isVisible: true,
|
||||
version: 1,
|
||||
animateLoading: true,
|
||||
responsiveBehavior: ResponsiveBehavior.Fill,
|
||||
buttonsList: {
|
||||
button1: {
|
||||
label: "Delete",
|
||||
isVisible: true,
|
||||
isDisabled: false,
|
||||
id: "button1",
|
||||
index: 0,
|
||||
buttonVariant: "outlined",
|
||||
buttonColor: "negative",
|
||||
},
|
||||
button2: {
|
||||
label: "Separator",
|
||||
isVisible: true,
|
||||
isDisabled: false,
|
||||
id: "button2",
|
||||
index: 1,
|
||||
itemType: "SEPARATOR",
|
||||
},
|
||||
button3: {
|
||||
label: "Cancel",
|
||||
isVisible: true,
|
||||
isDisabled: false,
|
||||
widgetId: "",
|
||||
id: "button3",
|
||||
index: 2,
|
||||
buttonVariant: "outlined",
|
||||
buttonColor: "accent",
|
||||
},
|
||||
button4: {
|
||||
label: "Save",
|
||||
isVisible: true,
|
||||
isDisabled: false,
|
||||
widgetId: "",
|
||||
id: "button4",
|
||||
index: 3,
|
||||
buttonVariant: "filled",
|
||||
buttonColor: "accent",
|
||||
},
|
||||
},
|
||||
} as unknown as WidgetDefaultProps;
|
||||
|
|
@ -4,7 +4,7 @@ import ThumbnailSVG from "../thumbnail.svg";
|
|||
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||
|
||||
export const metaConfig = {
|
||||
name: "Button Group",
|
||||
name: "Inline Buttons",
|
||||
iconSVG: IconSVG,
|
||||
thumbnailSVG: ThumbnailSVG,
|
||||
needsMeta: false,
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
import { BUTTON_VARIANTS, COLORS } from "@design-system/widgets";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import { capitalize } from "lodash";
|
||||
|
||||
export const propertyPaneContentConfig = [
|
||||
{
|
||||
sectionName: "Data",
|
||||
children: [
|
||||
{
|
||||
helpText: "Group Buttons",
|
||||
propertyName: "buttonsList",
|
||||
controlType: "GROUP_BUTTONS",
|
||||
allowSpatialGrouping: true,
|
||||
label: "Buttons",
|
||||
dependencies: ["childStylesheet", "orientation"],
|
||||
isBindProperty: false,
|
||||
isTriggerProperty: false,
|
||||
panelConfig: {
|
||||
editableTitle: true,
|
||||
titlePropertyName: "label",
|
||||
panelIdPropertyName: "id",
|
||||
updateHook: (
|
||||
props: any,
|
||||
propertyPath: string,
|
||||
propertyValue: string,
|
||||
) => {
|
||||
return [
|
||||
{
|
||||
propertyPath,
|
||||
propertyValue,
|
||||
},
|
||||
];
|
||||
},
|
||||
contentChildren: [
|
||||
{
|
||||
sectionName: "Label",
|
||||
children: [
|
||||
{
|
||||
propertyName: "label",
|
||||
helpText: "Sets the label of the button",
|
||||
label: "Text",
|
||||
controlType: "INPUT_TEXT",
|
||||
placeholderText: "Enter label",
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.TEXT },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionName: "General",
|
||||
children: [
|
||||
{
|
||||
propertyName: "isVisible",
|
||||
helpText: "Controls the visibility of the widget",
|
||||
label: "Visible",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.BOOLEAN },
|
||||
},
|
||||
{
|
||||
propertyName: "isDisabled",
|
||||
helpText: "Disables input to the widget",
|
||||
label: "Disabled",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.BOOLEAN },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionName: "Events",
|
||||
children: [
|
||||
{
|
||||
helpText: "when the button is clicked",
|
||||
propertyName: "onClick",
|
||||
label: "onClick",
|
||||
controlType: "ACTION_SELECTOR",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
styleChildren: [
|
||||
{
|
||||
sectionName: "General",
|
||||
children: [
|
||||
{
|
||||
propertyName: "buttonVariant",
|
||||
label: "Button variant",
|
||||
controlType: "ICON_TABS",
|
||||
fullWidth: true,
|
||||
helpText: "Sets the variant of the button",
|
||||
options: Object.values(BUTTON_VARIANTS).map((variant) => ({
|
||||
label: capitalize(variant),
|
||||
value: variant,
|
||||
})),
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
allowedValues: Object.values(BUTTON_VARIANTS),
|
||||
default: BUTTON_VARIANTS.filled,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
propertyName: "buttonColor",
|
||||
label: "Button color",
|
||||
controlType: "DROP_DOWN",
|
||||
fullWidth: true,
|
||||
helpText: "Sets the semantic color of the button",
|
||||
options: Object.values(COLORS).map((semantic) => ({
|
||||
label: capitalize(semantic),
|
||||
value: semantic,
|
||||
})),
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
allowedValues: Object.values(COLORS),
|
||||
default: COLORS.accent,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionName: "Icon",
|
||||
children: [
|
||||
{
|
||||
propertyName: "iconName",
|
||||
label: "Icon",
|
||||
helpText: "Sets the icon to be used for a button",
|
||||
controlType: "ICON_SELECT_V2",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.TEXT },
|
||||
},
|
||||
{
|
||||
propertyName: "iconAlign",
|
||||
label: "Position",
|
||||
helpText: "Sets the icon alignment of the button",
|
||||
controlType: "ICON_TABS",
|
||||
fullWidth: false,
|
||||
options: [
|
||||
{
|
||||
startIcon: "skip-left-line",
|
||||
value: "start",
|
||||
},
|
||||
{
|
||||
startIcon: "skip-right-line",
|
||||
value: "end",
|
||||
},
|
||||
],
|
||||
isBindProperty: false,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
allowedValues: ["start", "end"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionName: "General",
|
||||
children: [
|
||||
{
|
||||
helpText: "Controls the visibility of the widget",
|
||||
propertyName: "isVisible",
|
||||
label: "Visible",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.BOOLEAN },
|
||||
},
|
||||
{
|
||||
propertyName: "animateLoading",
|
||||
label: "Animate loading",
|
||||
controlType: "SWITCH",
|
||||
helpText: "Controls the loading of the widget",
|
||||
defaultValue: true,
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.BOOLEAN },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { propertyPaneContentConfig } from "./contentConfig";
|
||||
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
|
@ -0,0 +1,3 @@
|
|||
import { WDSInlineButtonsWidget } from "./widget";
|
||||
|
||||
export { WDSInlineButtonsWidget };
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,90 @@
|
|||
import React from "react";
|
||||
import type { SetterConfig } from "entities/AppTheming";
|
||||
import type { WidgetState } from "widgets/BaseWidget";
|
||||
import BaseWidget from "widgets/BaseWidget";
|
||||
import {
|
||||
metaConfig,
|
||||
defaultsConfig,
|
||||
autocompleteConfig,
|
||||
propertyPaneContentConfig,
|
||||
settersConfig,
|
||||
anvilConfig,
|
||||
} from "./../config";
|
||||
import type { InlineButtonsWidgetProps } from "./types";
|
||||
import { InlineButtonsComponent } from "../component";
|
||||
import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { AnvilConfig } from "WidgetProvider/constants";
|
||||
|
||||
class WDSInlineButtonsWidget extends BaseWidget<
|
||||
InlineButtonsWidgetProps,
|
||||
WidgetState
|
||||
> {
|
||||
constructor(props: InlineButtonsWidgetProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static type = "WDS_INLINE_BUTTONS_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
return metaConfig;
|
||||
}
|
||||
|
||||
static getDefaults() {
|
||||
return defaultsConfig;
|
||||
}
|
||||
|
||||
static getAutocompleteDefinitions() {
|
||||
return autocompleteConfig;
|
||||
}
|
||||
|
||||
static getPropertyPaneContentConfig() {
|
||||
return propertyPaneContentConfig;
|
||||
}
|
||||
|
||||
static getPropertyPaneStyleConfig() {
|
||||
return [];
|
||||
}
|
||||
|
||||
static getSetterConfig(): SetterConfig {
|
||||
return settersConfig;
|
||||
}
|
||||
|
||||
static getAnvilConfig(): AnvilConfig | null {
|
||||
return anvilConfig;
|
||||
}
|
||||
|
||||
onButtonClick = (
|
||||
onClick: string | undefined,
|
||||
callback?: (result: ExecutionResult) => void,
|
||||
) => {
|
||||
if (onClick) {
|
||||
super.executeAction({
|
||||
triggerPropertyName: "onClick",
|
||||
dynamicString: onClick,
|
||||
event: {
|
||||
type: EventType.ON_CLICK,
|
||||
callback: callback,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
getWidgetView() {
|
||||
return (
|
||||
<InlineButtonsComponent
|
||||
buttonsList={this.props.buttonsList}
|
||||
color={this.props.buttonColor}
|
||||
density={this.props.density}
|
||||
key={this.props.widgetId}
|
||||
onButtonClick={this.onButtonClick}
|
||||
orientation={this.props.orientation}
|
||||
variant={this.props.buttonVariant}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { WDSInlineButtonsWidget };
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { ButtonGroupProps } from "@design-system/widgets";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
import type { InlineButtonsItemComponentProps } from "../component/types";
|
||||
|
||||
export type ButtonsList = Record<string, InlineButtonsItemComponentProps>;
|
||||
|
||||
export interface InlineButtonsWidgetProps extends WidgetProps {
|
||||
buttonColor: ButtonGroupProps<object>["color"];
|
||||
buttonVariant: ButtonGroupProps<object>["variant"];
|
||||
orientation: ButtonGroupProps<object>["orientation"];
|
||||
isVisible: boolean;
|
||||
buttonsList: ButtonsList;
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><rect width="44" height="39" x="14.5" y="19.5" fill="#fff" rx="2.5"/><rect width="44" height="39" x="14.5" y="19.5" stroke="#CDD5DF" rx="2.5"/><rect width="16" height="6" x="22" y="27" fill="#CDD5DF" rx="1"/><path stroke="#CC3D00" stroke-linecap="round" stroke-linejoin="round" d="M22 50.5h2.5m2.5 0h-2.5m-2.5-8 2.5-2v10M35 50.5h-5v-2.646a2 2 0 0 1 1.257-1.857l2.486-.994A2 2 0 0 0 35 43.146V42.5a2 2 0 0 0-2-2h-3M38 40.5h3a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-1.715M38 50.5h3a2 2 0 0 0 2-2v-1a2 2 0 0 0-2-2h-2"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><rect width="56" height="44" x="8" y="16" fill="#fff" rx="3"/><rect width="32" height="6" x="16" y="24" fill="#E3E8EF" rx="1"/><path stroke="#CC3D00" stroke-linecap="round" stroke-linejoin="round" d="M16 50.5h2.5m2.5 0h-2.5m-2.5-8 2.5-2v10M29 50.5h-5v-2.646a2 2 0 0 1 1.257-1.857l2.486-.994A2 2 0 0 0 29 43.146V42.5a2 2 0 0 0-2-2h-3M32 40.5h3a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-1.715M32 50.5h3a2 2 0 0 0 2-2v-1a2 2 0 0 0-2-2h-2"/><rect width="12" height="12" x="43.5" y="39.5" fill="#FFEEE5" rx="1.5"/><rect width="12" height="12" x="43.5" y="39.5" stroke="#CC3D00" rx="1.5"/><path stroke="#CC3D00" stroke-linecap="round" d="m47 48 5-5"/><path stroke="#CC3D00" stroke-linecap="round" stroke-linejoin="round" d="M49 43h3v3"/></svg>
|
||||
|
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 801 B |
|
|
@ -2,14 +2,16 @@ import type { Key } from "react";
|
|||
import React, { useState } from "react";
|
||||
import { ActionGroup, Item } from "@design-system/widgets";
|
||||
import type {
|
||||
ButtonGroupComponentProps,
|
||||
ButtonGroupItemComponentProps,
|
||||
ToolbarButtonsComponentProps,
|
||||
ToolbarButtonsItemComponentProps,
|
||||
} from "./types";
|
||||
import { sortBy } from "lodash";
|
||||
|
||||
export const ButtonGroupComponent = (props: ButtonGroupComponentProps) => {
|
||||
export const ToolbarButtonsComponent = (
|
||||
props: ToolbarButtonsComponentProps,
|
||||
) => {
|
||||
const [loadingButtonIds, setLoadingButtonIds] = useState<
|
||||
Array<ButtonGroupItemComponentProps["id"]>
|
||||
Array<ToolbarButtonsItemComponentProps["id"]>
|
||||
>([]);
|
||||
|
||||
const sortedButtons = sortBy(
|
||||
|
|
@ -28,7 +30,7 @@ export const ButtonGroupComponent = (props: ButtonGroupComponentProps) => {
|
|||
})
|
||||
.map((button) => button.id);
|
||||
|
||||
const onActionComplete = (button: ButtonGroupItemComponentProps) => {
|
||||
const onActionComplete = (button: ToolbarButtonsItemComponentProps) => {
|
||||
const newLoadingButtonIds = [...loadingButtonIds];
|
||||
const index = newLoadingButtonIds.indexOf(button.id);
|
||||
|
||||
|
|
@ -58,7 +60,7 @@ export const ButtonGroupComponent = (props: ButtonGroupComponentProps) => {
|
|||
orientation={props.orientation}
|
||||
variant={props.variant}
|
||||
>
|
||||
{sortedButtons.map((button: ButtonGroupItemComponentProps) => {
|
||||
{sortedButtons.map((button: ToolbarButtonsItemComponentProps) => {
|
||||
return (
|
||||
<Item
|
||||
icon={button.iconName}
|
||||
|
|
@ -1,24 +1,24 @@
|
|||
import type {
|
||||
ActionGroupProps,
|
||||
ButtonGroupProps,
|
||||
ButtonProps,
|
||||
IconProps,
|
||||
} from "@design-system/widgets";
|
||||
import type { ButtonsList } from "../widget/types";
|
||||
import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
|
||||
export interface ButtonGroupComponentProps {
|
||||
color?: ActionGroupProps<object>["color"];
|
||||
variant?: ActionGroupProps<object>["variant"];
|
||||
orientation: ActionGroupProps<object>["orientation"];
|
||||
export interface ToolbarButtonsComponentProps {
|
||||
color?: ButtonGroupProps<object>["color"];
|
||||
variant?: ButtonGroupProps<object>["variant"];
|
||||
orientation: ButtonGroupProps<object>["orientation"];
|
||||
buttonsList: ButtonsList;
|
||||
onButtonClick: (
|
||||
onClick?: string,
|
||||
callback?: (result: ExecutionResult) => void,
|
||||
) => void;
|
||||
density?: ActionGroupProps<object>["density"];
|
||||
density?: ButtonGroupProps<object>["density"];
|
||||
}
|
||||
|
||||
export interface ButtonGroupItemComponentProps {
|
||||
export interface ToolbarButtonsItemComponentProps {
|
||||
label?: string;
|
||||
isVisible?: boolean;
|
||||
isLoading?: boolean;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import type { AnvilConfig } from "WidgetProvider/constants";
|
||||
|
||||
export const anvilConfig: AnvilConfig = {
|
||||
isLargeWidget: false,
|
||||
widgetSize: {
|
||||
minWidth: {
|
||||
base: "100%",
|
||||
"180px": "sizing-14",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
|
||||
|
||||
export const autocompleteConfig = {
|
||||
"!doc":
|
||||
"The Button group widget represents a set of buttons in a group. Group can have simple buttons or menu buttons with drop-down items.",
|
||||
"!url": "https://docs.appsmith.com/widget-reference/button-group",
|
||||
isVisible: DefaultAutocompleteDefinitions.isVisible,
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@ import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
|||
import type { WidgetDefaultProps } from "WidgetProvider/constants";
|
||||
|
||||
export const defaultsConfig = {
|
||||
widgetName: "ButtonGroup",
|
||||
widgetName: "ToolbarButtons",
|
||||
orientation: "horizontal",
|
||||
buttonVariant: "filled",
|
||||
buttonColor: "accent",
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export * from "./propertyPaneConfig";
|
||||
export { autocompleteConfig } from "./autocompleteConfig";
|
||||
export { defaultsConfig } from "./defaultsConfig";
|
||||
export { metaConfig } from "./metaConfig";
|
||||
export { settersConfig } from "./settersConfig";
|
||||
export { anvilConfig } from "./anvilConfig";
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import IconSVG from "../icon.svg";
|
||||
import ThumbnailSVG from "../thumbnail.svg";
|
||||
|
||||
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||
|
||||
export const metaConfig = {
|
||||
name: "Toolbar Buttons",
|
||||
iconSVG: IconSVG,
|
||||
thumbnailSVG: ThumbnailSVG,
|
||||
needsMeta: false,
|
||||
isCanvas: false,
|
||||
searchTags: ["click", "submit"],
|
||||
tags: [WIDGET_TAGS.BUTTONS],
|
||||
};
|
||||
|
|
@ -69,27 +69,6 @@ export const propertyPaneStyleConfig = [
|
|||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.TEXT },
|
||||
},
|
||||
{
|
||||
propertyName: "density",
|
||||
helpText: "Controls the density of the widget",
|
||||
label: "Density",
|
||||
controlType: "ICON_TABS",
|
||||
fullWidth: true,
|
||||
options: [
|
||||
{
|
||||
label: "Regular",
|
||||
value: "regular",
|
||||
},
|
||||
{
|
||||
label: "Compact",
|
||||
value: "compact",
|
||||
},
|
||||
],
|
||||
defaultValue: "regular",
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.TEXT },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
export const settersConfig = {
|
||||
__setters: {
|
||||
setVisibility: {
|
||||
path: "isVisible",
|
||||
type: "boolean",
|
||||
},
|
||||
setDisabled: {
|
||||
path: "isDisabled",
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="m3.5 7.5 1 1 1-2M9.5 8.5l2-2m0 2-2-2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M12.5 3.5h-10a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 341 B |
|
|
@ -0,0 +1,3 @@
|
|||
import { WDSToolbarButtonsWidget } from "./widget";
|
||||
|
||||
export { WDSToolbarButtonsWidget };
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><rect width="59" height="23" x="6.5" y="26.5" fill="#fff" rx="4.5"/><rect width="59" height="23" x="6.5" y="26.5" stroke="#CDD5DF" rx="4.5"/><rect width="24" height="15" x="10.5" y="30.5" fill="#FFEEE5" rx="2.5"/><rect width="24" height="15" x="10.5" y="30.5" stroke="#CC3D00" rx="2.5"/><path stroke="#CC3D00" stroke-linecap="round" stroke-linejoin="round" d="m18.5 37.5 3 3 5-5"/><rect width="24" height="15" x="37.5" y="30.5" fill="#FFEEE5" rx="2.5"/><rect width="24" height="15" x="37.5" y="30.5" stroke="#CC3D00" rx="2.5"/><path stroke="#CC3D00" stroke-linecap="round" d="m46.5 35 6 6m0-6-6 6"/></svg>
|
||||
|
After Width: | Height: | Size: 680 B |
|
|
@ -12,12 +12,12 @@ import {
|
|||
anvilConfig,
|
||||
} from "./../config";
|
||||
import type { ButtonGroupWidgetProps } from "./types";
|
||||
import { ButtonGroupComponent } from "../component";
|
||||
import { ToolbarButtonsComponent } from "../component";
|
||||
import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { AnvilConfig } from "WidgetProvider/constants";
|
||||
|
||||
class WDSButtonGroupWidget extends BaseWidget<
|
||||
class WDSToolbarButtonsWidget extends BaseWidget<
|
||||
ButtonGroupWidgetProps,
|
||||
WidgetState
|
||||
> {
|
||||
|
|
@ -25,7 +25,7 @@ class WDSButtonGroupWidget extends BaseWidget<
|
|||
super(props);
|
||||
}
|
||||
|
||||
static type = "WDS_BUTTON_GROUP_WIDGET";
|
||||
static type = "WDS_TOOLBAR_BUTTONS_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
return metaConfig;
|
||||
|
|
@ -75,7 +75,7 @@ class WDSButtonGroupWidget extends BaseWidget<
|
|||
|
||||
getWidgetView() {
|
||||
return (
|
||||
<ButtonGroupComponent
|
||||
<ToolbarButtonsComponent
|
||||
buttonsList={this.props.buttonsList}
|
||||
color={this.props.buttonColor}
|
||||
density={this.props.density}
|
||||
|
|
@ -88,4 +88,4 @@ class WDSButtonGroupWidget extends BaseWidget<
|
|||
}
|
||||
}
|
||||
|
||||
export { WDSButtonGroupWidget };
|
||||
export { WDSToolbarButtonsWidget };
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { ButtonGroupProps } from "@design-system/widgets";
|
||||
import type { WidgetProps } from "widgets/BaseWidget";
|
||||
import type { ToolbarButtonsItemComponentProps } from "../component/types";
|
||||
|
||||
export type ButtonsList = Record<string, ToolbarButtonsItemComponentProps>;
|
||||
|
||||
export interface ButtonGroupWidgetProps extends WidgetProps {
|
||||
buttonColor: ButtonGroupProps<object>["color"];
|
||||
buttonVariant: ButtonGroupProps<object>["variant"];
|
||||
orientation: ButtonGroupProps<object>["orientation"];
|
||||
isVisible: boolean;
|
||||
buttonsList: ButtonsList;
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ export const WDS_V2_WIDGET_MAP = {
|
|||
TEXT_WIDGET_V2: "WDS_HEADING_WIDGET",
|
||||
TABLE_WIDGET_V2: "WDS_TABLE_WIDGET",
|
||||
CURRENCY_INPUT_WIDGET: "WDS_CURRENCY_INPUT_WIDGET",
|
||||
BUTTON_GROUP_WIDGET: "WDS_BUTTON_GROUP_WIDGET",
|
||||
BUTTON_GROUP_WIDGET: "WDS_TOOLBAR_BUTTONS_WIDGET",
|
||||
PHONE_INPUT_WIDGET: "WDS_PHONE_INPUT_WIDGET",
|
||||
CHECKBOX_GROUP_WIDGET: "WDS_CHECKBOX_GROUP_WIDGET",
|
||||
SWITCH_WIDGET: "WDS_SWITCH_WIDGET",
|
||||
|
|
@ -20,6 +20,7 @@ export const WDS_V2_WIDGET_MAP = {
|
|||
MODAL_WIDGET: "WDS_MODAL_WIDGET",
|
||||
STATBOX_WIDGET: "WDS_STATBOX_WIDGET",
|
||||
KEY_VALUE_WIDGET: "WDS_KEY_VALUE_WIDGET",
|
||||
INLINE_BUTTONS_WIDGET: "WDS_INLINE_BUTTONS_WIDGET",
|
||||
|
||||
// Anvil layout widgets
|
||||
ZONE_WIDGET: anvilWidgets.ZONE_WIDGET,
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ export const getPlatformFunctions = () => {
|
|||
return platformFns;
|
||||
};
|
||||
|
||||
export const getEntityFunctions = () => {
|
||||
return entityFns;
|
||||
};
|
||||
|
||||
const platformFns = [
|
||||
{
|
||||
name: "navigateTo",
|
||||
|
|
@ -112,7 +116,7 @@ const platformFns = [
|
|||
},
|
||||
];
|
||||
|
||||
export const entityFns = [
|
||||
const entityFns = [
|
||||
{
|
||||
name: "run",
|
||||
qualifier: (entity: DataTreeEntity) => isRunNClearFnQualifierEntity(entity),
|
||||
|
|
|
|||
|
|
@ -35,5 +35,5 @@
|
|||
"importsNotUsedAsValues": "error"
|
||||
},
|
||||
"include": ["./src/**/*", "./packages"],
|
||||
"exclude": ["./packages/rts"]
|
||||
"exclude": ["./packages/rts", "**/node_modules", "**/.*/"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21441,9 +21441,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"ip@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "ip@npm:2.0.0"
|
||||
checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349
|
||||
version: 2.0.1
|
||||
resolution: "ip@npm:2.0.1"
|
||||
checksum: d765c9fd212b8a99023a4cde6a558a054c298d640fec1020567494d257afd78ca77e37126b1a3ef0e053646ced79a816bf50621d38d5e768cdde0431fa3b0d35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
|
|
@ -239,6 +239,13 @@
|
|||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core-micrometer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<version>1.74</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
66
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/RSAKeyUtil.java
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package com.appsmith.external.helpers;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Security;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.RSAPrivateKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class RSAKeyUtil {
|
||||
|
||||
static {
|
||||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||
}
|
||||
|
||||
private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
|
||||
private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----";
|
||||
|
||||
private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----";
|
||||
private static final String PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----";
|
||||
|
||||
public static boolean isPkcs1Key(String key) {
|
||||
return key.contains(PKCS_1_PEM_HEADER) && key.contains(PKCS_1_PEM_FOOTER);
|
||||
}
|
||||
|
||||
public static String replaceHeaderAndFooterFromKey(String key) {
|
||||
return key.replace(PKCS_1_PEM_HEADER, "")
|
||||
.replace(PKCS_1_PEM_FOOTER, "")
|
||||
.replace(PKCS_8_PEM_HEADER, "")
|
||||
.replace(PKCS_8_PEM_FOOTER, "")
|
||||
.replace("\n", "");
|
||||
}
|
||||
|
||||
public static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecurityException {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SunRsaSign");
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
|
||||
try {
|
||||
return keyFactory.generatePrivate(keySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new IllegalArgumentException("Unexpected key format! Accepted key formats are pkcs8, pkcs1", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PrivateKey readPkcs1PrivateKey(String base64EncodedPKCS1) throws Exception {
|
||||
// Decode the Base64-encoded PKCS#1 key
|
||||
byte[] pkcs1KeyBytes = Base64.getDecoder().decode(base64EncodedPKCS1);
|
||||
|
||||
// Use Bouncy Castle's ASN1 classes to parse the ASN.1 structure of the PKCS#1 key
|
||||
ASN1InputStream asn1InputStream = new ASN1InputStream(pkcs1KeyBytes);
|
||||
RSAPrivateKey rsaPrivateKey = RSAPrivateKey.getInstance(asn1InputStream.readObject());
|
||||
asn1InputStream.close();
|
||||
|
||||
// Convert ASN.1 structure to RSAPrivateKeySpec
|
||||
RSAPrivateKeySpec rsaPrivateKeySpec =
|
||||
new RSAPrivateKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent());
|
||||
|
||||
// Generate the PrivateKey object
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return keyFactory.generatePrivate(rsaPrivateKeySpec);
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ public class SSLDetails implements AppsmithDomain {
|
|||
|
||||
AuthType authType;
|
||||
|
||||
// For Mutual TLS of datasource integration
|
||||
CACertificateType caCertificateType;
|
||||
|
||||
UploadedFile keyFile;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.appsmith.external.services.SharedConfig;
|
|||
import com.external.plugins.datatypes.PostgresSpecificDataTypes;
|
||||
import com.external.plugins.exceptions.PostgresErrorMessages;
|
||||
import com.external.plugins.exceptions.PostgresPluginError;
|
||||
import com.external.plugins.utils.MutualTLSCertValidatingFactory;
|
||||
import com.external.plugins.utils.PostgresDatasourceUtils;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
|
@ -87,6 +88,7 @@ import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns;
|
|||
import static com.appsmith.external.helpers.PluginUtils.getPSParamLabel;
|
||||
import static com.appsmith.external.helpers.Sizeof.sizeof;
|
||||
import static com.appsmith.external.helpers.SmartSubstitutionHelper.replaceQuestionMarkWithDollarIndex;
|
||||
import static com.appsmith.external.models.SSLDetails.AuthType.VERIFY_CA;
|
||||
import static com.external.plugins.utils.PostgresDataTypeUtils.DataType.BOOL;
|
||||
import static com.external.plugins.utils.PostgresDataTypeUtils.DataType.DATE;
|
||||
import static com.external.plugins.utils.PostgresDataTypeUtils.DataType.DECIMAL;
|
||||
|
|
@ -1183,8 +1185,42 @@ public class PostgresPlugin extends BasePlugin {
|
|||
break;
|
||||
case DEFAULT:
|
||||
/* do nothing - accept default driver setting */
|
||||
break;
|
||||
|
||||
case VERIFY_CA:
|
||||
case VERIFY_FULL:
|
||||
config.addDataSourceProperty("ssl", "true");
|
||||
if (sslAuthType == SSLDetails.AuthType.VERIFY_FULL) {
|
||||
config.addDataSourceProperty("sslmode", "verify-full");
|
||||
} else {
|
||||
config.addDataSourceProperty("sslmode", "verify-ca");
|
||||
}
|
||||
// Common properties for both VERIFY_CA and VERIFY_FULL
|
||||
config.addDataSourceProperty("sslfactory", MutualTLSCertValidatingFactory.class.getName());
|
||||
config.addDataSourceProperty(
|
||||
"clientCertString",
|
||||
datasourceConfiguration
|
||||
.getConnection()
|
||||
.getSsl()
|
||||
.getCertificateFile()
|
||||
.getBase64Content());
|
||||
config.addDataSourceProperty(
|
||||
"clientKeyString",
|
||||
datasourceConfiguration
|
||||
.getConnection()
|
||||
.getSsl()
|
||||
.getKeyFile()
|
||||
.getBase64Content());
|
||||
config.addDataSourceProperty(
|
||||
"serverCACertString",
|
||||
datasourceConfiguration
|
||||
.getConnection()
|
||||
.getSsl()
|
||||
.getCaCertificateFile()
|
||||
.getBase64Content());
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new AppsmithPluginException(
|
||||
PostgresPluginError.POSTGRES_PLUGIN_ERROR,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package com.external.plugins.utils;
|
||||
|
||||
import com.appsmith.external.helpers.RSAKeyUtil;
|
||||
import org.postgresql.ssl.WrappedFactory;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MutualTLSCertValidatingFactory extends WrappedFactory {
|
||||
|
||||
public MutualTLSCertValidatingFactory(Properties info) throws Exception {
|
||||
// Convert String certificates and keys to objects
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
|
||||
// Client certificate
|
||||
ByteArrayInputStream clientCertIS =
|
||||
new ByteArrayInputStream(Base64.getDecoder().decode(info.getProperty("clientCertString")));
|
||||
X509Certificate clientCertificate = (X509Certificate) cf.generateCertificate(clientCertIS);
|
||||
|
||||
// Client key and this assumes we are using RSA keys
|
||||
PrivateKey privateKey;
|
||||
|
||||
if (RSAKeyUtil.isPkcs1Key(info.getProperty("clientKeyString"))) {
|
||||
String rsaKey = RSAKeyUtil.replaceHeaderAndFooterFromKey(info.getProperty("clientKeyString"));
|
||||
privateKey = RSAKeyUtil.readPkcs1PrivateKey(rsaKey);
|
||||
} else {
|
||||
String rsaKey = RSAKeyUtil.replaceHeaderAndFooterFromKey(info.getProperty("clientKeyString"));
|
||||
privateKey = RSAKeyUtil.readPkcs8PrivateKey(Base64.getDecoder().decode(rsaKey));
|
||||
}
|
||||
|
||||
// CA certificate for verifying the server
|
||||
ByteArrayInputStream caCertIS =
|
||||
new ByteArrayInputStream(Base64.getDecoder().decode(info.getProperty("serverCACertString")));
|
||||
X509Certificate caCertificate = (X509Certificate) cf.generateCertificate(caCertIS);
|
||||
|
||||
// Client keystore
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null, null);
|
||||
keyStore.setCertificateEntry("client-cert", clientCertificate);
|
||||
keyStore.setKeyEntry("client-key", privateKey, "password".toCharArray(), new java.security.cert.Certificate[] {
|
||||
clientCertificate
|
||||
});
|
||||
|
||||
// Truststore
|
||||
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
trustStore.load(null, null);
|
||||
trustStore.setCertificateEntry("ca-cert", caCertificate);
|
||||
|
||||
// Initialize SSLContext
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(trustStore);
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, "password".toCharArray());
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
|
||||
|
||||
this.factory = sslContext.getSocketFactory();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +1,55 @@
|
|||
package com.appsmith.server.helpers.ce.bridge;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import lombok.NonNull;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class Bridge extends Criteria {
|
||||
private final List<Criteria> criteriaList = new ArrayList<>();
|
||||
|
||||
public final class Bridge {
|
||||
private Bridge() {}
|
||||
|
||||
public static Bridge bridge() {
|
||||
return new Bridge();
|
||||
public static <T extends BaseDomain> BridgeQuery<T> query() {
|
||||
return new BridgeQuery<>();
|
||||
}
|
||||
|
||||
public Bridge equal(@NonNull String key, @NonNull String value) {
|
||||
criteriaList.add(Criteria.where(key).is(value));
|
||||
return this;
|
||||
@Deprecated
|
||||
public static <T extends BaseDomain> BridgeQuery<T> bridge() {
|
||||
return new BridgeQuery<>();
|
||||
}
|
||||
|
||||
public Bridge equal(@NonNull String key, @NonNull ObjectId value) {
|
||||
criteriaList.add(Criteria.where(key).is(value));
|
||||
return this;
|
||||
@SafeVarargs
|
||||
public static <T extends BaseDomain> BridgeQuery<T> or(BridgeQuery<T>... items) {
|
||||
final BridgeQuery<T> q = new BridgeQuery<>();
|
||||
q.checks.add(new Criteria().orOperator(items));
|
||||
return q;
|
||||
}
|
||||
|
||||
public Bridge in(@NonNull String key, @NonNull Collection<String> value) {
|
||||
criteriaList.add(Criteria.where(key).in(value));
|
||||
return this;
|
||||
@SafeVarargs
|
||||
public static <T extends BaseDomain> BridgeQuery<T> and(BridgeQuery<T>... items) {
|
||||
final BridgeQuery<T> q = new BridgeQuery<>();
|
||||
q.checks.add(new Criteria().andOperator(items));
|
||||
return q;
|
||||
}
|
||||
|
||||
public Bridge exists(@NonNull String key) {
|
||||
criteriaList.add(Criteria.where(key).exists(true));
|
||||
return this;
|
||||
public static <T extends BaseDomain> BridgeQuery<T> equal(@NonNull String key, @NonNull String value) {
|
||||
return Bridge.<T>query().equal(key, value);
|
||||
}
|
||||
|
||||
public Bridge isTrue(@NonNull String key) {
|
||||
criteriaList.add(Criteria.where(key).is(true));
|
||||
return this;
|
||||
public static <T extends BaseDomain> BridgeQuery<T> equal(@NonNull String key, @NonNull ObjectId value) {
|
||||
return Bridge.<T>query().equal(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly disable the `where()` API to prevent its usage. This is because querying with this API will work here,
|
||||
* but won't work in the Postgres version.
|
||||
*/
|
||||
public static Bridge where() {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
public static <T extends BaseDomain> BridgeQuery<T> in(@NonNull String key, @NonNull Collection<String> value) {
|
||||
return Bridge.<T>query().in(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getCriteriaObject() {
|
||||
if (criteriaList.isEmpty()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Empty bridge criteria leads to subtle bugs. Just don't call `.criteria()` in such cases.");
|
||||
}
|
||||
public static <T extends BaseDomain> BridgeQuery<T> exists(@NonNull String key) {
|
||||
return Bridge.<T>query().exists(key);
|
||||
}
|
||||
|
||||
return new Criteria().andOperator(criteriaList.toArray(new Criteria[0])).getCriteriaObject();
|
||||
public static <T extends BaseDomain> BridgeQuery<T> isTrue(@NonNull String key) {
|
||||
return Bridge.<T>query().isTrue(key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package com.appsmith.server.helpers.ce.bridge;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import lombok.NonNull;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public final class BridgeQuery<T extends BaseDomain> extends Criteria {
|
||||
final List<Criteria> checks = new ArrayList<>();
|
||||
|
||||
BridgeQuery() {}
|
||||
|
||||
public BridgeQuery<T> equal(@NonNull String key, @NonNull String value) {
|
||||
checks.add(Criteria.where(key).is(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BridgeQuery<T> equal(@NonNull String key, @NonNull ObjectId value) {
|
||||
checks.add(Criteria.where(key).is(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BridgeQuery<T> in(@NonNull String key, @NonNull Collection<String> value) {
|
||||
checks.add(Criteria.where(key).in(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BridgeQuery<T> exists(@NonNull String key) {
|
||||
checks.add(Criteria.where(key).exists(true));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BridgeQuery<T> isTrue(@NonNull String key) {
|
||||
checks.add(Criteria.where(key).is(true));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BridgeQuery<T> or(BridgeQuery... items) {
|
||||
checks.add(new Criteria().orOperator(items));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BridgeQuery<T> and(BridgeQuery... items) {
|
||||
checks.add(new Criteria().andOperator(items));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly disable the `where()` API to prevent its usage. This is because querying with this API will work here,
|
||||
* but won't work in the Postgres version.
|
||||
*/
|
||||
public static Bridge where() {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getCriteriaObject() {
|
||||
if (checks.isEmpty()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Empty bridge criteria leads to subtle bugs. Just don't call `.criteria()` in such cases.");
|
||||
}
|
||||
|
||||
return new Criteria().andOperator(checks.toArray(new Criteria[0])).getCriteriaObject();
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.appsmith.server.dtos.WorkspacePluginStatus;
|
|||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.ce.bridge.Bridge;
|
||||
import com.appsmith.server.helpers.ce.bridge.BridgeQuery;
|
||||
import com.appsmith.server.repositories.PluginRepository;
|
||||
import com.appsmith.server.services.AnalyticsService;
|
||||
import com.appsmith.server.services.BaseService;
|
||||
|
|
@ -55,8 +56,6 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.server.helpers.ce.bridge.Bridge.bridge;
|
||||
|
||||
@Slf4j
|
||||
public class PluginServiceCEImpl extends BaseService<PluginRepository, Plugin, String> implements PluginServiceCE {
|
||||
|
||||
|
|
@ -131,7 +130,7 @@ public class PluginServiceCEImpl extends BaseService<PluginRepository, Plugin, S
|
|||
List<String> pluginIds = org.getPlugins().stream()
|
||||
.map(WorkspacePlugin::getPluginId)
|
||||
.collect(Collectors.toList());
|
||||
final Bridge criteria = bridge().in(FieldName.ID, pluginIds);
|
||||
final BridgeQuery<Plugin> criteria = Bridge.in(FieldName.ID, pluginIds);
|
||||
|
||||
final String typeString = params.getFirst(FieldName.TYPE);
|
||||
if (typeString != null) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.appsmith.server.acl.AclPermission;
|
|||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationPage;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.helpers.ce.bridge.Bridge;
|
||||
import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl;
|
||||
import com.appsmith.server.repositories.CacheableRepositoryHelper;
|
||||
import com.appsmith.server.solutions.ApplicationPermission;
|
||||
|
|
@ -25,7 +26,6 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.server.helpers.ce.bridge.Bridge.bridge;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
@Slf4j
|
||||
|
|
@ -55,7 +55,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
public Mono<Application> findByIdAndWorkspaceId(String id, String workspaceId, AclPermission permission) {
|
||||
return queryBuilder()
|
||||
.byId(id)
|
||||
.criteria(bridge().equal(Application.Fields.workspaceId, workspaceId))
|
||||
.criteria(Bridge.equal(Application.Fields.workspaceId, workspaceId))
|
||||
.permission(permission)
|
||||
.one();
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
@Override
|
||||
public Mono<Application> findByName(String name, AclPermission permission) {
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.name, name))
|
||||
.criteria(Bridge.equal(Application.Fields.name, name))
|
||||
.permission(permission)
|
||||
.one();
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
@Override
|
||||
public Flux<Application> findByWorkspaceId(String workspaceId, AclPermission permission) {
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.workspaceId, workspaceId))
|
||||
.criteria(Bridge.equal(Application.Fields.workspaceId, workspaceId))
|
||||
.permission(permission)
|
||||
.all();
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
@Override
|
||||
public Flux<Application> findByClonedFromApplicationId(String applicationId, AclPermission permission) {
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.clonedFromApplicationId, applicationId))
|
||||
.criteria(Bridge.equal(Application.Fields.clonedFromApplicationId, applicationId))
|
||||
.permission(permission)
|
||||
.all();
|
||||
}
|
||||
|
|
@ -140,12 +140,12 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
|
||||
final Mono<Integer> setAllAsNonDefaultMono = queryBuilder()
|
||||
.byId(applicationId)
|
||||
.criteria(bridge().isTrue("pages.isDefault"))
|
||||
.criteria(Bridge.isTrue("pages.isDefault"))
|
||||
.updateFirst(new Update().set("pages.$.isDefault", false));
|
||||
|
||||
final Mono<Integer> setDefaultMono = queryBuilder()
|
||||
.byId(applicationId)
|
||||
.criteria(bridge().equal("pages._id", new ObjectId(pageId)))
|
||||
.criteria(Bridge.equal("pages._id", new ObjectId(pageId)))
|
||||
.updateFirst(new Update().set("pages.$.isDefault", true));
|
||||
|
||||
return setAllAsNonDefaultMono.then(setDefaultMono).then();
|
||||
|
|
@ -166,7 +166,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
AclPermission aclPermission) {
|
||||
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(
|
||||
.criteria(Bridge.equal(
|
||||
Application.Fields.gitApplicationMetadata_defaultApplicationId, defaultApplicationId)
|
||||
.equal(Application.Fields.gitApplicationMetadata_branchName, branchName))
|
||||
.fields(projectionFieldNames)
|
||||
|
|
@ -179,7 +179,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
String defaultApplicationId, String branchName, Optional<AclPermission> aclPermission) {
|
||||
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(
|
||||
.criteria(Bridge.equal(
|
||||
Application.Fields.gitApplicationMetadata_defaultApplicationId, defaultApplicationId)
|
||||
.equal(Application.Fields.gitApplicationMetadata_branchName, branchName))
|
||||
.permission(aclPermission.orElse(null))
|
||||
|
|
@ -191,8 +191,8 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
String defaultApplicationId, AclPermission permission) {
|
||||
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(
|
||||
Application.Fields.gitApplicationMetadata_defaultApplicationId, defaultApplicationId))
|
||||
.criteria(Bridge.equal(
|
||||
Application.Fields.gitApplicationMetadata_defaultApplicationId, defaultApplicationId))
|
||||
.permission(permission)
|
||||
.all();
|
||||
}
|
||||
|
|
@ -200,14 +200,14 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
@Override
|
||||
public Mono<Long> countByWorkspaceId(String workspaceId) {
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.workspaceId, workspaceId))
|
||||
.criteria(Bridge.equal(Application.Fields.workspaceId, workspaceId))
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> getGitConnectedApplicationWithPrivateRepoCount(String workspaceId) {
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.workspaceId, workspaceId)
|
||||
.criteria(Bridge.equal(Application.Fields.workspaceId, workspaceId)
|
||||
.isTrue(Application.Fields.gitApplicationMetadata_isRepoPrivate))
|
||||
.count();
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
public Flux<Application> getGitConnectedApplicationByWorkspaceId(String workspaceId) {
|
||||
AclPermission aclPermission = applicationPermission.getEditPermission();
|
||||
return queryBuilder()
|
||||
.criteria(bridge()
|
||||
.criteria(Bridge
|
||||
// isRepoPrivate and gitAuth will be stored only with default application which ensures we will
|
||||
// have only single application per repo
|
||||
.exists(Application.Fields.gitApplicationMetadata_isRepoPrivate)
|
||||
|
|
@ -230,8 +230,8 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
public Mono<Application> getApplicationByDefaultApplicationIdAndDefaultBranch(String defaultApplicationId) {
|
||||
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(
|
||||
Application.Fields.gitApplicationMetadata_defaultApplicationId, defaultApplicationId))
|
||||
.criteria(Bridge.equal(
|
||||
Application.Fields.gitApplicationMetadata_defaultApplicationId, defaultApplicationId))
|
||||
.one();
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
@Override
|
||||
public Mono<Long> countByNameAndWorkspaceId(String applicationName, String workspaceId, AclPermission permission) {
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.workspaceId, workspaceId)
|
||||
.criteria(Bridge.equal(Application.Fields.workspaceId, workspaceId)
|
||||
.equal(Application.Fields.name, applicationName))
|
||||
.permission(permission)
|
||||
.count();
|
||||
|
|
@ -298,7 +298,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp
|
|||
Update unsetProtected = new Update().set(isProtectedFieldPath, false);
|
||||
|
||||
return queryBuilder()
|
||||
.criteria(bridge().equal(Application.Fields.gitApplicationMetadata_defaultApplicationId, applicationId))
|
||||
.criteria(Bridge.equal(Application.Fields.gitApplicationMetadata_defaultApplicationId, applicationId))
|
||||
.permission(permission)
|
||||
.updateAll(unsetProtected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.appsmith.server.repositories.ce;
|
|||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Config;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.helpers.ce.bridge.Bridge;
|
||||
import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl;
|
||||
import com.appsmith.server.repositories.CacheableRepositoryHelper;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
|
|
@ -10,7 +11,6 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
|
|||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static com.appsmith.server.helpers.ce.bridge.Bridge.bridge;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
public class CustomConfigRepositoryCEImpl extends BaseAppsmithRepositoryImpl<Config>
|
||||
|
|
@ -32,7 +32,7 @@ public class CustomConfigRepositoryCEImpl extends BaseAppsmithRepositoryImpl<Con
|
|||
@Override
|
||||
public Mono<Config> findByNameAsUser(String name, User user, AclPermission permission) {
|
||||
return getAllPermissionGroupsForUser(user).flatMap(permissionGroups -> queryBuilder()
|
||||
.criteria(bridge().equal(Config.Fields.name, name))
|
||||
.criteria(Bridge.equal(Config.Fields.name, name))
|
||||
.permission(permission)
|
||||
.permissionGroups(permissionGroups)
|
||||
.one());
|
||||
|
|
|
|||