@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.2

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.
Files changed (78) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +1 -1
  3. package/dist/assets/index-DD9fzkbz.js +232 -0
  4. package/dist/index.html +70 -0
  5. package/index.html +0 -1
  6. package/npm-shrinkwrap.json +6329 -0
  7. package/package.json +95 -51
  8. package/CHANGELOG.md +0 -120
  9. package/eslint.config.js +0 -23
  10. package/public/vite.svg +0 -1
  11. package/src/app/AppShell.tsx +0 -1733
  12. package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
  13. package/src/assets/react.svg +0 -1
  14. package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
  15. package/src/features/fetchxml/api/excelExport.ts +0 -318
  16. package/src/features/fetchxml/api/formattedValues.ts +0 -107
  17. package/src/features/fetchxml/api/pptbClient.ts +0 -2635
  18. package/src/features/fetchxml/model/displaySettings.ts +0 -18
  19. package/src/features/fetchxml/model/fetchxml.ts +0 -346
  20. package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
  21. package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
  22. package/src/features/fetchxml/model/layoutxml.ts +0 -372
  23. package/src/features/fetchxml/model/nodes.ts +0 -255
  24. package/src/features/fetchxml/model/operators.ts +0 -624
  25. package/src/features/fetchxml/model/treeUtils.ts +0 -174
  26. package/src/features/fetchxml/state/builderStore.tsx +0 -1160
  27. package/src/features/fetchxml/state/cache.ts +0 -393
  28. package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
  29. package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
  30. package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
  31. package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
  32. package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
  33. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
  34. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
  35. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
  36. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
  37. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
  38. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
  39. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
  40. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
  41. package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
  42. package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
  43. package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
  44. package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
  45. package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
  46. package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
  47. package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
  48. package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
  49. package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
  50. package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
  51. package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
  52. package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
  53. package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
  54. package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
  55. package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
  56. package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
  57. package/src/main.tsx +0 -11
  58. package/src/shared/components/AttributePicker.tsx +0 -138
  59. package/src/shared/components/BetweenInputPicker.tsx +0 -120
  60. package/src/shared/components/BooleanValuePicker.tsx +0 -165
  61. package/src/shared/components/DateTimeInputPicker.tsx +0 -170
  62. package/src/shared/components/DynamicValueList.tsx +0 -128
  63. package/src/shared/components/ExcelIcon.tsx +0 -30
  64. package/src/shared/components/MultiValuePicker.tsx +0 -178
  65. package/src/shared/components/NumericInputPicker.tsx +0 -156
  66. package/src/shared/components/OptionSetValuePicker.tsx +0 -150
  67. package/src/shared/components/RelationshipPicker.tsx +0 -268
  68. package/src/shared/contexts/ThemeContext.tsx +0 -19
  69. package/src/shared/hooks/useAccessMode.ts +0 -77
  70. package/src/shared/hooks/useLazyMetadata.ts +0 -132
  71. package/src/shared/hooks/usePptbContext.ts +0 -170
  72. package/src/shared/hooks/usePublisherFilter.ts +0 -214
  73. package/src/shared/hooks/useSolutionFilter.ts +0 -162
  74. package/src/shared/utils/debug.ts +0 -87
  75. package/tsconfig.app.json +0 -28
  76. package/tsconfig.json +0 -7
  77. package/tsconfig.node.json +0 -26
  78. 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
- }