@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/assets/index-DD9fzkbz.js +232 -0
- package/dist/index.html +70 -0
- package/index.html +0 -1
- package/npm-shrinkwrap.json +6329 -0
- package/package.json +95 -51
- package/CHANGELOG.md +0 -120
- package/eslint.config.js +0 -23
- package/public/vite.svg +0 -1
- package/src/app/AppShell.tsx +0 -1733
- package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
- package/src/assets/react.svg +0 -1
- package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
- package/src/features/fetchxml/api/excelExport.ts +0 -318
- package/src/features/fetchxml/api/formattedValues.ts +0 -107
- package/src/features/fetchxml/api/pptbClient.ts +0 -2635
- package/src/features/fetchxml/model/displaySettings.ts +0 -18
- package/src/features/fetchxml/model/fetchxml.ts +0 -346
- package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
- package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
- package/src/features/fetchxml/model/layoutxml.ts +0 -372
- package/src/features/fetchxml/model/nodes.ts +0 -255
- package/src/features/fetchxml/model/operators.ts +0 -624
- package/src/features/fetchxml/model/treeUtils.ts +0 -174
- package/src/features/fetchxml/state/builderStore.tsx +0 -1160
- package/src/features/fetchxml/state/cache.ts +0 -393
- package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
- package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
- package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
- package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
- package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
- package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
- package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
- package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
- package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
- package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
- package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
- package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
- package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
- package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
- package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
- package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
- package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
- package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
- package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
- package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
- package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
- package/src/main.tsx +0 -11
- package/src/shared/components/AttributePicker.tsx +0 -138
- package/src/shared/components/BetweenInputPicker.tsx +0 -120
- package/src/shared/components/BooleanValuePicker.tsx +0 -165
- package/src/shared/components/DateTimeInputPicker.tsx +0 -170
- package/src/shared/components/DynamicValueList.tsx +0 -128
- package/src/shared/components/ExcelIcon.tsx +0 -30
- package/src/shared/components/MultiValuePicker.tsx +0 -178
- package/src/shared/components/NumericInputPicker.tsx +0 -156
- package/src/shared/components/OptionSetValuePicker.tsx +0 -150
- package/src/shared/components/RelationshipPicker.tsx +0 -268
- package/src/shared/contexts/ThemeContext.tsx +0 -19
- package/src/shared/hooks/useAccessMode.ts +0 -77
- package/src/shared/hooks/useLazyMetadata.ts +0 -132
- package/src/shared/hooks/usePptbContext.ts +0 -170
- package/src/shared/hooks/usePublisherFilter.ts +0 -214
- package/src/shared/hooks/useSolutionFilter.ts +0 -162
- package/src/shared/utils/debug.ts +0 -87
- package/tsconfig.app.json +0 -28
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -7
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Property editor for Condition nodes
|
|
3
|
-
* Controls filter criteria: attribute, operator, value
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useEffect, useMemo } from "react";
|
|
7
|
-
import {
|
|
8
|
-
Field,
|
|
9
|
-
Input,
|
|
10
|
-
Dropdown,
|
|
11
|
-
Option,
|
|
12
|
-
Label,
|
|
13
|
-
Tooltip,
|
|
14
|
-
Switch,
|
|
15
|
-
makeStyles,
|
|
16
|
-
tokens,
|
|
17
|
-
} from "@fluentui/react-components";
|
|
18
|
-
import { Info16Regular } from "@fluentui/react-icons";
|
|
19
|
-
import type { ConditionNode, FetchNode } from "../../../../model/nodes";
|
|
20
|
-
import { AttributePicker } from "../../../../../../shared/components/AttributePicker";
|
|
21
|
-
import { OptionSetValuePicker } from "../../../../../../shared/components/OptionSetValuePicker";
|
|
22
|
-
import { BooleanValuePicker } from "../../../../../../shared/components/BooleanValuePicker";
|
|
23
|
-
import { NumericInputPicker } from "../../../../../../shared/components/NumericInputPicker";
|
|
24
|
-
import { DateTimeInputPicker } from "../../../../../../shared/components/DateTimeInputPicker";
|
|
25
|
-
import { BetweenInputPicker } from "../../../../../../shared/components/BetweenInputPicker";
|
|
26
|
-
import { MultiValuePicker } from "../../../../../../shared/components/MultiValuePicker";
|
|
27
|
-
import {
|
|
28
|
-
getOperatorsForAttributeType,
|
|
29
|
-
operatorRequiresValue,
|
|
30
|
-
type OperatorDefinition,
|
|
31
|
-
} from "../../../../model/operators";
|
|
32
|
-
import {
|
|
33
|
-
loadEntityAttributes,
|
|
34
|
-
loadAttributeDetailedMetadata,
|
|
35
|
-
} from "../../../../api/dataverseMetadata";
|
|
36
|
-
import type { AttributeMetadata } from "../../../../api/pptbClient";
|
|
37
|
-
import {
|
|
38
|
-
collectLinkEntityReferences,
|
|
39
|
-
isConditionInRootEntityFilter,
|
|
40
|
-
} from "../../../../model/treeUtils";
|
|
41
|
-
import { ValueOfPicker } from "./ValueOfPicker";
|
|
42
|
-
|
|
43
|
-
const useStyles = makeStyles({
|
|
44
|
-
container: {
|
|
45
|
-
display: "flex",
|
|
46
|
-
flexDirection: "column",
|
|
47
|
-
gap: tokens.spacingVerticalM,
|
|
48
|
-
padding: tokens.spacingVerticalM,
|
|
49
|
-
},
|
|
50
|
-
section: {
|
|
51
|
-
display: "flex",
|
|
52
|
-
flexDirection: "column",
|
|
53
|
-
gap: tokens.spacingVerticalS,
|
|
54
|
-
},
|
|
55
|
-
fieldWithTooltip: {
|
|
56
|
-
display: "flex",
|
|
57
|
-
alignItems: "center",
|
|
58
|
-
gap: tokens.spacingHorizontalXS,
|
|
59
|
-
},
|
|
60
|
-
tooltipIcon: {
|
|
61
|
-
color: tokens.colorNeutralForeground3,
|
|
62
|
-
cursor: "help",
|
|
63
|
-
},
|
|
64
|
-
compareToggle: {
|
|
65
|
-
display: "flex",
|
|
66
|
-
alignItems: "center",
|
|
67
|
-
gap: tokens.spacingHorizontalS,
|
|
68
|
-
marginBottom: tokens.spacingVerticalXS,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
interface ConditionEditorProps {
|
|
73
|
-
node: ConditionNode;
|
|
74
|
-
entityName: string;
|
|
75
|
-
onUpdate: (updates: Record<string, unknown>) => void;
|
|
76
|
-
isAggregateQuery?: boolean;
|
|
77
|
-
fetchQuery?: FetchNode | null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Returns the exact attribute type for valueof comparisons.
|
|
82
|
-
* Strict matching: source and target attributes must have the same type.
|
|
83
|
-
* e.g., Money can only compare to Money, Integer to Integer, etc.
|
|
84
|
-
*/
|
|
85
|
-
function getCompatibleTypesForValueof(attributeType: string | undefined): string[] {
|
|
86
|
-
if (!attributeType) return [];
|
|
87
|
-
|
|
88
|
-
// Strict type matching - return only the exact same type
|
|
89
|
-
// This ensures Money compares to Money, Integer to Integer, etc.
|
|
90
|
-
return [attributeType];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function ConditionEditor({
|
|
94
|
-
node,
|
|
95
|
-
entityName,
|
|
96
|
-
onUpdate,
|
|
97
|
-
isAggregateQuery = false,
|
|
98
|
-
fetchQuery = null,
|
|
99
|
-
}: ConditionEditorProps) {
|
|
100
|
-
const styles = useStyles();
|
|
101
|
-
|
|
102
|
-
// State for filtered operators based on selected attribute type
|
|
103
|
-
const [availableOperators, setAvailableOperators] = useState<OperatorDefinition[]>([]);
|
|
104
|
-
// State for selected attribute metadata
|
|
105
|
-
const [selectedAttribute, setSelectedAttribute] = useState<AttributeMetadata | null>(null);
|
|
106
|
-
|
|
107
|
-
// Collect available link-entity references for entityname picker
|
|
108
|
-
const linkEntityReferences = useMemo(() => collectLinkEntityReferences(fetchQuery), [fetchQuery]);
|
|
109
|
-
|
|
110
|
-
// Check if this condition is in the root entity's filter (where entityname is applicable)
|
|
111
|
-
const isInRootFilter = useMemo(
|
|
112
|
-
() => isConditionInRootEntityFilter(fetchQuery, node.id),
|
|
113
|
-
[fetchQuery, node.id]
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// Determine the effective entity name for attribute loading
|
|
117
|
-
// If entityname is set, use the linked entity's name; otherwise use the parent entity
|
|
118
|
-
const effectiveEntityName = useMemo(() => {
|
|
119
|
-
if (node.entityname && isInRootFilter) {
|
|
120
|
-
// Find the link-entity reference and get its entity name
|
|
121
|
-
const ref = linkEntityReferences.find((r) => r.identifier === node.entityname);
|
|
122
|
-
if (ref) {
|
|
123
|
-
return ref.entityName;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return entityName;
|
|
127
|
-
}, [node.entityname, isInRootFilter, linkEntityReferences, entityName]);
|
|
128
|
-
|
|
129
|
-
// Get compatible attribute types for valueof comparison
|
|
130
|
-
const compatibleTypesForValueof = useMemo(
|
|
131
|
-
() => getCompatibleTypesForValueof(selectedAttribute?.AttributeType),
|
|
132
|
-
[selectedAttribute?.AttributeType]
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
// Load attribute metadata when attribute changes to get its type
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
if (!node.attribute || !effectiveEntityName) {
|
|
138
|
-
// No attribute selected - show all operators
|
|
139
|
-
setAvailableOperators(getOperatorsForAttributeType(undefined));
|
|
140
|
-
setSelectedAttribute(null);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Load entity attributes to find the selected attribute's type
|
|
145
|
-
loadEntityAttributes(effectiveEntityName)
|
|
146
|
-
.then(async (attributes: AttributeMetadata[]) => {
|
|
147
|
-
const attr = attributes.find((a) => a.LogicalName === node.attribute);
|
|
148
|
-
const type = attr?.AttributeType;
|
|
149
|
-
|
|
150
|
-
// For numeric and datetime types, load detailed metadata
|
|
151
|
-
const needsDetailedMetadata =
|
|
152
|
-
type === "Integer" ||
|
|
153
|
-
type === "BigInt" ||
|
|
154
|
-
type === "Decimal" ||
|
|
155
|
-
type === "Double" ||
|
|
156
|
-
type === "DateTime";
|
|
157
|
-
|
|
158
|
-
let detailedAttr = attr;
|
|
159
|
-
if (needsDetailedMetadata && attr) {
|
|
160
|
-
try {
|
|
161
|
-
// Load detailed metadata with type casting to get MinValue, MaxValue, Precision, Format
|
|
162
|
-
detailedAttr = await loadAttributeDetailedMetadata(entityName, node.attribute, type);
|
|
163
|
-
console.log(`Loaded detailed metadata for ${node.attribute}:`, detailedAttr);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.error(`Failed to load detailed metadata for ${node.attribute}:`, error);
|
|
166
|
-
// Fall back to basic metadata
|
|
167
|
-
detailedAttr = attr;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Store the full attribute metadata (with detailed properties if available)
|
|
172
|
-
setSelectedAttribute(detailedAttr || null);
|
|
173
|
-
|
|
174
|
-
// Filter operators based on attribute type
|
|
175
|
-
let operators = getOperatorsForAttributeType(type);
|
|
176
|
-
|
|
177
|
-
// For DateTime with DateOnly behavior, exclude operators that require hours/minutes
|
|
178
|
-
if (type === "DateTime" && detailedAttr) {
|
|
179
|
-
const dateTimeBehavior = detailedAttr.DateTimeBehavior?.Value;
|
|
180
|
-
const format = detailedAttr.Format;
|
|
181
|
-
|
|
182
|
-
// DateOnly format or UserLocal behavior with DateOnly format should exclude hour/minute operators
|
|
183
|
-
const isDateOnly = format === "DateOnly" || dateTimeBehavior === "DateOnly";
|
|
184
|
-
|
|
185
|
-
if (isDateOnly) {
|
|
186
|
-
const excludedOperators = [
|
|
187
|
-
"last-x-hours",
|
|
188
|
-
"next-x-hours",
|
|
189
|
-
"olderthan-x-hours",
|
|
190
|
-
"olderthan-x-minutes",
|
|
191
|
-
];
|
|
192
|
-
operators = operators.filter((op) => !excludedOperators.includes(op.value));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
setAvailableOperators(operators);
|
|
197
|
-
})
|
|
198
|
-
.catch((error) => {
|
|
199
|
-
console.error("Failed to load attribute metadata for operator filtering:", error);
|
|
200
|
-
// Fallback to all operators on error
|
|
201
|
-
setAvailableOperators(getOperatorsForAttributeType(undefined));
|
|
202
|
-
setSelectedAttribute(null);
|
|
203
|
-
});
|
|
204
|
-
}, [node.attribute, effectiveEntityName]);
|
|
205
|
-
|
|
206
|
-
const handleTextChange = (field: string) => (_: unknown, data: { value: string }) => {
|
|
207
|
-
onUpdate({ [field]: data.value || undefined });
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const handleDropdownChange = (field: string) => (_: unknown, data: { optionValue?: string }) => {
|
|
211
|
-
onUpdate({ [field]: data.optionValue });
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const handleAttributeChange = (logicalName: string | undefined) => {
|
|
215
|
-
// When attribute is cleared or changed, reset operator and value
|
|
216
|
-
if (!logicalName) {
|
|
217
|
-
onUpdate({ attribute: undefined, operator: undefined, value: undefined });
|
|
218
|
-
} else {
|
|
219
|
-
onUpdate({ attribute: logicalName, operator: undefined, value: undefined });
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const handleEntityNameChange = (identifier: string | undefined) => {
|
|
224
|
-
// When entityname changes, also clear attribute, operator, and value
|
|
225
|
-
// since the attribute must come from the selected entity
|
|
226
|
-
onUpdate({
|
|
227
|
-
entityname: identifier || undefined,
|
|
228
|
-
attribute: undefined,
|
|
229
|
-
operator: undefined,
|
|
230
|
-
value: undefined,
|
|
231
|
-
});
|
|
232
|
-
};
|
|
233
|
-
// Check if the current operator requires a value (using the smart function)
|
|
234
|
-
const requiresValue = operatorRequiresValue(node.operator);
|
|
235
|
-
|
|
236
|
-
// Get the operator definition to check for multi-value requirements
|
|
237
|
-
const operatorDef = availableOperators.find((op) => op.value === node.operator);
|
|
238
|
-
const requiresTwoValues = operatorDef?.requiresTwoValues ?? false;
|
|
239
|
-
const requiresMultipleValues = operatorDef?.requiresMultipleValues ?? false;
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<div className={styles.container}>
|
|
243
|
-
{/* Basic Properties */}
|
|
244
|
-
<div className={styles.section}>
|
|
245
|
-
<Label weight="semibold">Condition Properties</Label>
|
|
246
|
-
<Field label="Attribute Name" required>
|
|
247
|
-
<div className={styles.fieldWithTooltip}>
|
|
248
|
-
<AttributePicker
|
|
249
|
-
entityLogicalName={effectiveEntityName}
|
|
250
|
-
value={node.attribute}
|
|
251
|
-
onChange={handleAttributeChange}
|
|
252
|
-
placeholder="Select or type attribute name"
|
|
253
|
-
/>
|
|
254
|
-
<Tooltip
|
|
255
|
-
content={
|
|
256
|
-
node.entityname
|
|
257
|
-
? `Logical name of the attribute from the linked entity "${node.entityname}".`
|
|
258
|
-
: "Logical name of the attribute to filter on. Must exist in the parent entity."
|
|
259
|
-
}
|
|
260
|
-
relationship="description"
|
|
261
|
-
>
|
|
262
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
263
|
-
</Tooltip>
|
|
264
|
-
</div>
|
|
265
|
-
</Field>
|
|
266
|
-
<Field label="Operator" required>
|
|
267
|
-
<div className={styles.fieldWithTooltip}>
|
|
268
|
-
<Dropdown
|
|
269
|
-
value={node.operator}
|
|
270
|
-
selectedOptions={[node.operator]}
|
|
271
|
-
onOptionSelect={handleDropdownChange("operator")}
|
|
272
|
-
placeholder="Select operator"
|
|
273
|
-
>
|
|
274
|
-
{availableOperators.map((op) => (
|
|
275
|
-
<Option key={op.value} value={op.value}>
|
|
276
|
-
{op.label}
|
|
277
|
-
</Option>
|
|
278
|
-
))}
|
|
279
|
-
</Dropdown>
|
|
280
|
-
<Tooltip
|
|
281
|
-
content="Comparison operator. Available operators depend on attribute type (string, number, datetime, lookup, etc.)."
|
|
282
|
-
relationship="description"
|
|
283
|
-
>
|
|
284
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
285
|
-
</Tooltip>
|
|
286
|
-
</div>
|
|
287
|
-
</Field>
|
|
288
|
-
{requiresValue && (
|
|
289
|
-
<>
|
|
290
|
-
{/* Multi-value operators don't support valueof */}
|
|
291
|
-
{requiresMultipleValues && selectedAttribute ? (
|
|
292
|
-
<Field label="Values">
|
|
293
|
-
<div className={styles.fieldWithTooltip}>
|
|
294
|
-
<MultiValuePicker
|
|
295
|
-
entityLogicalName={effectiveEntityName}
|
|
296
|
-
attributeLogicalName={node.attribute}
|
|
297
|
-
attribute={selectedAttribute}
|
|
298
|
-
value={Array.isArray(node.value) ? node.value : undefined}
|
|
299
|
-
onChange={(val) => onUpdate({ value: val })}
|
|
300
|
-
placeholder="Enter values (comma-separated)"
|
|
301
|
-
/>
|
|
302
|
-
<Tooltip
|
|
303
|
-
content="Enter multiple values separated by commas."
|
|
304
|
-
relationship="description"
|
|
305
|
-
>
|
|
306
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
307
|
-
</Tooltip>
|
|
308
|
-
</div>
|
|
309
|
-
</Field>
|
|
310
|
-
) : /* Dual-value operators don't support valueof */
|
|
311
|
-
requiresTwoValues && selectedAttribute ? (
|
|
312
|
-
<Field label="Value Range">
|
|
313
|
-
<div className={styles.fieldWithTooltip}>
|
|
314
|
-
<BetweenInputPicker
|
|
315
|
-
attribute={selectedAttribute}
|
|
316
|
-
value={
|
|
317
|
-
Array.isArray(node.value) && node.value.length === 2
|
|
318
|
-
? [node.value[0], node.value[1]]
|
|
319
|
-
: [undefined, undefined]
|
|
320
|
-
}
|
|
321
|
-
onChange={(val) => onUpdate({ value: val })}
|
|
322
|
-
placeholder1="Start value"
|
|
323
|
-
placeholder2="End value"
|
|
324
|
-
/>
|
|
325
|
-
<Tooltip
|
|
326
|
-
content="Enter start and end values for the range."
|
|
327
|
-
relationship="description"
|
|
328
|
-
>
|
|
329
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
330
|
-
</Tooltip>
|
|
331
|
-
</div>
|
|
332
|
-
</Field>
|
|
333
|
-
) : (
|
|
334
|
-
/* Single-value operators support valueof comparison */
|
|
335
|
-
<>
|
|
336
|
-
{/* Toggle between literal value and compare to column */}
|
|
337
|
-
<div className={styles.compareToggle}>
|
|
338
|
-
<Switch
|
|
339
|
-
checked={node.valueof !== undefined}
|
|
340
|
-
onChange={(_ev, data) => {
|
|
341
|
-
if (data.checked) {
|
|
342
|
-
// Switching to compare mode - clear value, set valueof to empty
|
|
343
|
-
onUpdate({ value: undefined, valueof: "" });
|
|
344
|
-
} else {
|
|
345
|
-
// Switching to literal mode - clear valueof
|
|
346
|
-
onUpdate({ valueof: undefined, value: undefined });
|
|
347
|
-
}
|
|
348
|
-
}}
|
|
349
|
-
/>
|
|
350
|
-
<Label size="small">
|
|
351
|
-
{node.valueof !== undefined ? "Compare to column" : "Literal value"}
|
|
352
|
-
</Label>
|
|
353
|
-
<Tooltip
|
|
354
|
-
content="Toggle between comparing to a literal value or another column's value (same row)."
|
|
355
|
-
relationship="description"
|
|
356
|
-
>
|
|
357
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
358
|
-
</Tooltip>
|
|
359
|
-
</div>
|
|
360
|
-
|
|
361
|
-
{node.valueof !== undefined ? (
|
|
362
|
-
/* Compare to column mode - show entity selector + attribute picker */
|
|
363
|
-
<ValueOfPicker
|
|
364
|
-
fetchQuery={fetchQuery}
|
|
365
|
-
rootEntityName={fetchQuery?.entity?.name || ""}
|
|
366
|
-
effectiveEntityName={effectiveEntityName}
|
|
367
|
-
valueof={node.valueof || ""}
|
|
368
|
-
compatibleTypes={compatibleTypesForValueof}
|
|
369
|
-
onChange={(valueof) => {
|
|
370
|
-
// Keep valueof as empty string (not undefined) when entity changes
|
|
371
|
-
// to prevent the switch from reverting to literal mode
|
|
372
|
-
onUpdate({ valueof: valueof ?? undefined });
|
|
373
|
-
}}
|
|
374
|
-
/>
|
|
375
|
-
) : (
|
|
376
|
-
/* Literal value mode - show type-specific pickers */
|
|
377
|
-
<Field label="Value">
|
|
378
|
-
<div className={styles.fieldWithTooltip}>
|
|
379
|
-
{selectedAttribute?.AttributeType === "Picklist" ||
|
|
380
|
-
selectedAttribute?.AttributeType === "State" ||
|
|
381
|
-
selectedAttribute?.AttributeType === "Status" ? (
|
|
382
|
-
<OptionSetValuePicker
|
|
383
|
-
entityLogicalName={effectiveEntityName}
|
|
384
|
-
attributeLogicalName={node.attribute}
|
|
385
|
-
value={typeof node.value === "number" ? node.value : undefined}
|
|
386
|
-
onChange={(val) => onUpdate({ value: val })}
|
|
387
|
-
placeholder="Select a value"
|
|
388
|
-
/>
|
|
389
|
-
) : selectedAttribute?.AttributeType === "Boolean" ? (
|
|
390
|
-
<BooleanValuePicker
|
|
391
|
-
entityLogicalName={effectiveEntityName}
|
|
392
|
-
attributeLogicalName={node.attribute}
|
|
393
|
-
value={
|
|
394
|
-
typeof node.value === "boolean"
|
|
395
|
-
? node.value
|
|
396
|
-
: typeof node.value === "string"
|
|
397
|
-
? node.value === "true" || node.value === "1"
|
|
398
|
-
: undefined
|
|
399
|
-
}
|
|
400
|
-
onChange={(val) => onUpdate({ value: val })}
|
|
401
|
-
placeholder="Select true or false"
|
|
402
|
-
/>
|
|
403
|
-
) : selectedAttribute?.AttributeType === "Integer" ||
|
|
404
|
-
selectedAttribute?.AttributeType === "BigInt" ||
|
|
405
|
-
selectedAttribute?.AttributeType === "Decimal" ||
|
|
406
|
-
selectedAttribute?.AttributeType === "Double" ? (
|
|
407
|
-
<NumericInputPicker
|
|
408
|
-
attribute={selectedAttribute}
|
|
409
|
-
value={typeof node.value === "number" ? node.value : undefined}
|
|
410
|
-
onChange={(val) => onUpdate({ value: val })}
|
|
411
|
-
placeholder="Enter a number"
|
|
412
|
-
/>
|
|
413
|
-
) : selectedAttribute?.AttributeType === "DateTime" ? (
|
|
414
|
-
<DateTimeInputPicker
|
|
415
|
-
attribute={selectedAttribute}
|
|
416
|
-
value={
|
|
417
|
-
typeof node.value === "string"
|
|
418
|
-
? node.value
|
|
419
|
-
: node.value instanceof Date
|
|
420
|
-
? node.value
|
|
421
|
-
: undefined
|
|
422
|
-
}
|
|
423
|
-
onChange={(val) => onUpdate({ value: val?.toISOString() })}
|
|
424
|
-
placeholder="Select a date"
|
|
425
|
-
/>
|
|
426
|
-
) : (
|
|
427
|
-
<Input
|
|
428
|
-
value={
|
|
429
|
-
typeof node.value === "string" || typeof node.value === "number"
|
|
430
|
-
? String(node.value)
|
|
431
|
-
: ""
|
|
432
|
-
}
|
|
433
|
-
onChange={handleTextChange("value")}
|
|
434
|
-
placeholder="Filter value"
|
|
435
|
-
/>
|
|
436
|
-
)}
|
|
437
|
-
<Tooltip
|
|
438
|
-
content={
|
|
439
|
-
selectedAttribute?.AttributeType === "Picklist" ||
|
|
440
|
-
selectedAttribute?.AttributeType === "State" ||
|
|
441
|
-
selectedAttribute?.AttributeType === "Status"
|
|
442
|
-
? "Select a value from the available options."
|
|
443
|
-
: selectedAttribute?.AttributeType === "Boolean"
|
|
444
|
-
? "Select true or false."
|
|
445
|
-
: selectedAttribute?.AttributeType === "Integer" ||
|
|
446
|
-
selectedAttribute?.AttributeType === "BigInt" ||
|
|
447
|
-
selectedAttribute?.AttributeType === "Decimal" ||
|
|
448
|
-
selectedAttribute?.AttributeType === "Double"
|
|
449
|
-
? "Enter a numeric value."
|
|
450
|
-
: selectedAttribute?.AttributeType === "DateTime"
|
|
451
|
-
? "Select a date and optionally a time."
|
|
452
|
-
: "Value to compare against."
|
|
453
|
-
}
|
|
454
|
-
relationship="description"
|
|
455
|
-
>
|
|
456
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
457
|
-
</Tooltip>
|
|
458
|
-
</div>
|
|
459
|
-
</Field>
|
|
460
|
-
)}
|
|
461
|
-
</>
|
|
462
|
-
)}
|
|
463
|
-
</>
|
|
464
|
-
)}
|
|
465
|
-
</div>
|
|
466
|
-
|
|
467
|
-
{/* Advanced Section */}
|
|
468
|
-
<div className={styles.section}>
|
|
469
|
-
<Label weight="semibold">Advanced</Label>
|
|
470
|
-
|
|
471
|
-
{/* Entity Name - only show for conditions in root entity filters where there are link-entities */}
|
|
472
|
-
{isInRootFilter && linkEntityReferences.length > 0 && (
|
|
473
|
-
<Field
|
|
474
|
-
label="Entity Name (optional)"
|
|
475
|
-
hint="Filter on a column from a linked entity (outer join scenario)."
|
|
476
|
-
>
|
|
477
|
-
<div className={styles.fieldWithTooltip}>
|
|
478
|
-
<Dropdown
|
|
479
|
-
value={node.entityname ?? ""}
|
|
480
|
-
selectedOptions={node.entityname ? [node.entityname] : []}
|
|
481
|
-
onOptionSelect={(_ev, data) => {
|
|
482
|
-
const value = data.optionValue;
|
|
483
|
-
handleEntityNameChange(value === "" ? undefined : value);
|
|
484
|
-
}}
|
|
485
|
-
placeholder="Select linked entity..."
|
|
486
|
-
>
|
|
487
|
-
<Option value="">None (current entity)</Option>
|
|
488
|
-
{linkEntityReferences.map((ref) => (
|
|
489
|
-
<Option key={ref.identifier} value={ref.identifier}>
|
|
490
|
-
{ref.displayLabel}
|
|
491
|
-
</Option>
|
|
492
|
-
))}
|
|
493
|
-
</Dropdown>
|
|
494
|
-
<Tooltip
|
|
495
|
-
content="When filtering on an outer-joined link-entity, specify which entity's column to filter. Use the alias if defined, otherwise the entity name."
|
|
496
|
-
relationship="description"
|
|
497
|
-
>
|
|
498
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
499
|
-
</Tooltip>
|
|
500
|
-
</div>
|
|
501
|
-
</Field>
|
|
502
|
-
)}
|
|
503
|
-
|
|
504
|
-
{isAggregateQuery && (
|
|
505
|
-
<Field label="Aggregate Function (optional)">
|
|
506
|
-
<div className={styles.fieldWithTooltip}>
|
|
507
|
-
<Dropdown
|
|
508
|
-
value={node.aggregate ?? "none"}
|
|
509
|
-
selectedOptions={[node.aggregate ?? "none"]}
|
|
510
|
-
onOptionSelect={handleDropdownChange("aggregate")}
|
|
511
|
-
placeholder="None"
|
|
512
|
-
>
|
|
513
|
-
<Option value="none">None</Option>
|
|
514
|
-
<Option value="count">Count</Option>
|
|
515
|
-
<Option value="countcolumn">Count Column</Option>
|
|
516
|
-
<Option value="sum">Sum</Option>
|
|
517
|
-
<Option value="avg">Average</Option>
|
|
518
|
-
<Option value="min">Minimum</Option>
|
|
519
|
-
<Option value="max">Maximum</Option>
|
|
520
|
-
</Dropdown>
|
|
521
|
-
<Tooltip
|
|
522
|
-
content="Apply aggregate function before comparing. Used in HAVING clauses for aggregate queries."
|
|
523
|
-
relationship="description"
|
|
524
|
-
>
|
|
525
|
-
<Info16Regular className={styles.tooltipIcon} />
|
|
526
|
-
</Tooltip>
|
|
527
|
-
</div>
|
|
528
|
-
</Field>
|
|
529
|
-
)}
|
|
530
|
-
</div>
|
|
531
|
-
</div>
|
|
532
|
-
);
|
|
533
|
-
}
|