Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
d5f45d9
UI & Layout changes for the empty state of the query builder
teboho Feb 17, 2026
8b56737
Merge branch 'main' into teboho/e/4346
teboho Mar 5, 2026
9e63a73
Merge branch 'main' into teboho/e/4346
teboho Mar 12, 2026
9becd2e
queryBuilder): add ValueSources component and IgnoreIfUnassigned widget
teboho Mar 17, 2026
45b852c
Merge branch 'main' of https://github.com/shesha-io/shesha-framework …
teboho Mar 19, 2026
ad97a6f
add support for JsonLogic expressions in deepMergeValues function
teboho Mar 19, 2026
5bc85ac
feat: add mustache expression evaluation and utility functions
teboho Mar 26, 2026
0e81c68
Align query builder control layout
teboho Mar 26, 2026
fe2dfaa
feat: implement expression context building from metadata and enhance…
teboho Mar 27, 2026
30877e4
reduce modal size
teboho Mar 29, 2026
ad2182b
unify source selector and tighten rule/value cell styling
teboho Mar 31, 2026
9ede58e
This keeps OBJECT, NUMBER, etc. vertically centered inside the colore…
teboho Mar 31, 2026
e54d383
The empty-state buttons are now aligned to the start of their contain…
teboho Mar 31, 2026
6a4a41f
query builder field-source fx expression + build type fixes
teboho Mar 31, 2026
3c5555f
undo qb styles
teboho Mar 31, 2026
6228b34
replace library-rendered query builder with custom builder component
teboho Apr 1, 2026
a16fde1
query builder layout fixes — rule spacing, boolean toggle, rail, and …
teboho Apr 1, 2026
5f48d1b
replace boolean segmented with custom pill button group
teboho Apr 1, 2026
3b6fc56
boolean fields use value-only source with no operator/value-src selector
teboho Apr 1, 2026
17f6c69
tighten expression editor focus selectors to custom builder DOM path
teboho Apr 1, 2026
2497db0
remove unused content
teboho Apr 1, 2026
f467639
add function source to query builder field side
teboho Apr 1, 2026
da3ddd3
wrap rule row layout at deep nesting levels
teboho Apr 1, 2026
9d82da0
fix typed constants in LINQ converter, unify empty operators, and aud…
teboho Apr 1, 2026
9861f52
remove unused var
teboho Apr 1, 2026
4a0a20b
add safe fallbacks for ConfigUtils API in query builder
teboho Apr 2, 2026
f84e949
widen query builder field column and fix date picker input alignment
teboho Apr 2, 2026
35e4859
Replacing inline empty-state markup with a dedicated QueryRuleElement…
teboho Apr 2, 2026
c37e792
refine query builder empty state, value editor layout, and expression…
teboho Apr 2, 2026
dc0c2ab
Refine query builder UI: group nesting limits, item shell layout, and…
teboho Apr 7, 2026
543113e
expression editor: float inline on focus, expand into antd Modal with…
teboho Apr 7, 2026
271565b
Portal inline expression editor to document body to avoid overflow cl…
teboho Apr 7, 2026
ceb622e
The >> caret icon is now hidden by default (display: none) and only s…
teboho Apr 7, 2026
6f430d0
Fix group logic label to account for OR child relations and allow dra…
teboho Apr 7, 2026
98cbce1
Add stopPropagation to drag-over and drop handlers to prevent parent …
teboho Apr 7, 2026
1f00616
Improve query builder layout: fixed-width rule rows with horizontal s…
teboho Apr 7, 2026
ec1d46e
rm unused
teboho Apr 7, 2026
2c077ca
remove unused code
teboho Apr 7, 2026
593e45b
Merge branch 'main' of https://github.com/shesha-io/shesha-framework …
teboho Apr 9, 2026
215fb33
fix build
teboho Apr 9, 2026
833abf1
Merge remote-tracking branch 'origin/main' into teboho/e/4346
James-Baloyi May 14, 2026
0e46078
Merge branch 'main' into james/b/updated-qb-branch
James-Baloyi May 22, 2026
d2eaf13
Merge branch 'main' into james/b/updated-qb-branch
James-Baloyi Jun 1, 2026
72c4f3b
move horizontal scrollbar to query builder parent
James-Baloyi Jun 1, 2026
07223af
fix: build
James-Baloyi Jun 1, 2026
7e62108
fix: build
James-Baloyi Jun 1, 2026
b955bce
resolve merge conflicts
James-Baloyi Jun 18, 2026
17a28ec
fix: build errors
James-Baloyi Jun 18, 2026
b47a305
fix: duplicate expression editor exports
James-Baloyi Jun 18, 2026
b0cc8d8
fix: resolve strict-mode type errors in query builder for rollup build
James-Baloyi Jun 18, 2026
670adc5
test: align StringField_IsEmpty_Convert with null-or-whitespace is-em…
James-Baloyi Jun 18, 2026
b9d4d1e
fix: address CodeRabbit findings on PR #4993
James-Baloyi Jun 22, 2026
6d6ec46
fix: merge conflicts, address code rabbit comments
James-Baloyi Jun 22, 2026
f134afb
Merge branch 'james/b/updated-qb-branch' of https://github.com/James-…
James-Baloyi Jun 22, 2026
d547fcf
fix: return empty array for unevaluatedExpressions on empty expression
James-Baloyi Jun 23, 2026
82af62a
address code-rabbit issues
James-Baloyi Jun 23, 2026
4feb17c
fix: stop infinite render loop in query builder value editors
James-Baloyi Jun 23, 2026
9ba617b
fix infinite state calls when updating query builder values
James-Baloyi Jun 24, 2026
01456e1
address persistent slowness and lag
James-Baloyi Jun 24, 2026
5c04547
refactor: remove duplicate inline type annotation on FunctionValueEditor
James-Baloyi Jun 25, 2026
ace435f
Merge branch 'main' into james/b/updated-qb-branch
James-Baloyi Jul 30, 2026
0a50cb6
address PR comments
James-Baloyi Jul 30, 2026
6250bb8
clean up queryBuilderContent.tsx diff
James-Baloyi Jul 30, 2026
0c10c06
fix component crash when changing mode
James-Baloyi Jul 30, 2026
98d92d3
fix 'fieldSettings' crash issue
James-Baloyi Jul 30, 2026
de0a514
scale query builder layout and unify control styling
James-Baloyi Jul 30, 2026
b152c90
clean up no arg functions
James-Baloyi Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
440 changes: 246 additions & 194 deletions shesha-core/src/Shesha.Framework/JsonLogic/JsonLogic2LinqConverter.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public async Task StringField_NotLike_FetchAsync()
public void StringField_IsEmpty_Convert()
{
var expression = ConvertToExpression<Person>(_stringField_IsEmpty_expression);
Assert.Equal(@"ent => (ent.FirstName == null)", expression?.ToInvariantString());
Assert.Equal(@"ent => ((ent.FirstName == null) OrElse (ent.FirstName.Trim() == """"))", expression?.ToInvariantString());
}

[Fact]
Expand Down
2 changes: 2 additions & 0 deletions shesha-reactjs/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ const baseTsConfig = {
ignores: [
"src/apis/*",
"**/__tests__/**/*",
"**/*.test.ts",
"**/*.test.tsx",
],
plugins: {
"memory-monitor": memoryTracePlugin,
Expand Down
9 changes: 4 additions & 5 deletions shesha-reactjs/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,24 @@ module.exports = {
// extended assertions to Jest
setupFilesAfterEnv: [
//"@testing-library/jest-dom/extend-expect"
"@testing-library/jest-dom"
"@testing-library/jest-dom",
"<rootDir>/src/testSetup.ts",
],

// Test spec file resolution pattern
// Matches parent folder `__tests__` and filename
// should contain `test` or `spec`.
//testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$",
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|tsx)$',
// other Jest configuration options...
/*
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
*/
// other Jest configuration options...
// Module file extensions for importing
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json'
}
}
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { DataTypes } from '@/interfaces';
import { IObjectMetadata } from '@/interfaces/metadata';
import { buildExpressionContextFromMetadata } from './contextMetadata';

describe('buildExpressionContextFromMetadata', () => {
it('merges nested properties with type-definition members for context suggestions', async () => {
const metadata: IObjectMetadata = {
dataType: DataTypes.object,
properties: [
{
path: 'contexts',
dataType: DataTypes.object,
properties: [
{
path: 'canvasContext',
dataType: DataTypes.object,
properties: [
{ path: 'zoom', dataType: DataTypes.number },
{ path: 'details.themeName', dataType: DataTypes.string },
],
typeDefinitionLoader: async () => ({
typeName: 'ICanvasContextApi',
files: [{
fileName: 'apis/canvasContext.ts',
content: `
export interface ICanvasActions {
setDesignerDevice(deviceType: string): void;
setCanvasWidth(width: number | string): void;
}

export interface ICanvasState {
activeDevice?: string;
}

export interface ICanvasContextApi extends ICanvasState {
zoom?: number;
api: ICanvasActions;
}
`,
}],
}),
},
],
},
],
};

await expect(buildExpressionContextFromMetadata(metadata)).resolves.toEqual({
contexts: {
canvasContext: {
zoom: null,
details: {
themeName: null,
},
activeDevice: null,
api: {
setDesignerDevice: null,
setCanvasWidth: null,
},
},
},
});
});
});
20 changes: 11 additions & 9 deletions shesha-reactjs/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
export { default as BasicDisplayFormItem, type IBasicDisplayFormItemProps } from './basicDisplayFormItem';
export {
ExpressionEditor,
buildExpressionContextFromPaths,
type ExpressionContext,
type ExpressionContextValue,
type ExpressionEditorProps,
type ExpressionFunctionDefinition,
type BuildExpressionContextFromPathsOptions,
} from './expressionEditor';
export {
buildExpressionContextFromMetadata,
buildExpressionContextFromProperties,
Expand Down Expand Up @@ -118,3 +109,14 @@ export { ConfigurableComponentRenderer } from "./configurableComponentRenderer";
export { PermissionAutocomplete, type IPermissionAutocompleteProps } from './permissionAutocomplete';
export { ConfigItemAutocomplete } from './configurableItemAutocomplete';
export { RefListSimpleDropdown as RefListDropdown } from './queryBuilder/widgets/refListDropDown/simpleDropdown';
export {
ExpressionEditor,
buildExpressionContextFromPaths,
type ExpressionContext,
type ExpressionContextValue,
type ExpressionEditorProps,
type ExpressionFunctionArg,
type ExpressionFunctionCategory,
type ExpressionFunctionDefinition,
type BuildExpressionContextFromPathsOptions,
} from './expressionEditor';
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
ISpecification,
metadataHasNestedProperties,
} from '@/interfaces/metadata';
import { Select, Tooltip } from 'antd';
import { Select, SelectProps, Tooltip } from 'antd';
import { SizeType } from 'antd/lib/config-provider/SizeContext';
import { useQueryBuilder } from '@/providers';
import { isNonEmptyArray } from '@/utils/array';
Expand All @@ -33,6 +33,8 @@ export interface IPropertySelectProps {
style?: CSSProperties;
dropdownStyle?: CSSProperties;
size?: SizeType;
placeholder?: string;
variant?: SelectProps['variant'];
onChange?: (value: string | null) => void;
onSelect?: (value: string, selectedProperty: IPropertyItem | undefined) => void;
readOnly?: boolean;
Expand All @@ -49,6 +51,7 @@ export interface IQbItem {
interface IOption extends DefaultOptionType {
value: string;
label: string | React.ReactNode;
displayTitle?: string;
}

interface IAutocompleteState {
Expand All @@ -57,6 +60,38 @@ interface IAutocompleteState {
prefix: string;
}

const propertyOptionContainerStyle: CSSProperties = {
display: 'inline-flex',
alignItems: 'center',
gap: 8,
width: '100%',
maxWidth: '100%',
minWidth: 0,
};

const propertyOptionIconStyle: CSSProperties = {
flex: '0 0 auto',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
};

const propertyOptionTextStyle: CSSProperties = {
flex: '1 1 auto',
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};

const getOptionTitle = (option: IOption | undefined): string | undefined => {
if (!option)
return undefined;

return option.displayTitle ??
(typeof option.label === 'string' ? option.label : option.value);
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const getFullPath = (path: string, prefix: string | null): string => {
return prefix ? `${prefix}.${camelcase(path)}` : camelcase(path);
};
Expand Down Expand Up @@ -89,27 +124,36 @@ const propertyItem2option = (item: IPropertyItem, prefix: string, isSelectable:
if (isSpecification(item)) {
const value = item.name;
const label = (
<div>
{item.description ? <Tooltip title={item.description}><BulbTwoTone twoToneColor="orange" style={{ cursor: 'help' }} /></Tooltip> : <BulbOutlined />}
{item.friendlyName}
</div>
<span className="sha-property-select-option" title={item.friendlyName} style={propertyOptionContainerStyle}>
<span className="sha-property-select-option-icon" style={propertyOptionIconStyle}>
{item.description ? <Tooltip title={item.description}><BulbTwoTone twoToneColor="orange" style={{ cursor: 'help' }} /></Tooltip> : <BulbOutlined />}
</span>
<span className="sha-property-select-option-text" style={propertyOptionTextStyle}>{item.friendlyName}</span>
</span>
);

return {
value: value,
label: label,
displayTitle: item.friendlyName,
};
}

if (isPropertyMetadata(item)) {
const property = item as IPropertyMetadata;
const value = getPropertyItemIdentifier(item, prefix);
const icon = getIconByPropertyMetadata(property);
const label = <div>{icon} {value}</div>;
const label = (
<span className="sha-property-select-option" title={value} style={propertyOptionContainerStyle}>
<span className="sha-property-select-option-icon" style={propertyOptionIconStyle}>{icon}</span>
<span className="sha-property-select-option-text" style={propertyOptionTextStyle}>{value}</span>
</span>
);

return {
value: value,
label: label,
displayTitle: value,
disabled: isSelectable ? !isSelectable(item) : false,
};
}
Expand Down Expand Up @@ -236,8 +280,15 @@ export const PropertySelect: FC<IPropertySelectProps> = ({ readOnly = false, isP
props.onChange?.(null);
};

const selectedOption = useMemo(
() => state.options.find((option) => option.value === props.value),
[props.value, state.options],
);
const title = getOptionTitle(selectedOption) ?? props.value ?? props.placeholder;

return (
<Select<string>
{...(title !== undefined ? { title } : {})}
onSelect={onSelect}
value={props.value ?? null}
showSearch={{
Expand All @@ -246,13 +297,14 @@ export const PropertySelect: FC<IPropertySelectProps> = ({ readOnly = false, isP
size={props.size}
disabled={readOnly}
options={state.options}
style={{ minWidth: "150px" }}
style={{ minWidth: "150px", ...props.style }}
{...(props.dropdownStyle ? { styles: { popup: { root: props.dropdownStyle } } } : {})}
{...(props.variant ? { variant: props.variant } : {})}
popupMatchSelectWidth={false}
allowClear
onClear={onClear}
>
</Select>
placeholder={props.placeholder}
/>
);
};

Expand Down
Loading
Loading