@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,372 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LayoutXML Parser and Generator
|
|
3
|
-
* Handles Dataverse view layout configuration
|
|
4
|
-
*
|
|
5
|
-
* LayoutXML structure:
|
|
6
|
-
* <grid name="resultset" object="1" jump="name" select="1" icon="1" preview="1">
|
|
7
|
-
* <row name="result" id="accountid">
|
|
8
|
-
* <cell name="name" width="300" />
|
|
9
|
-
* <cell name="primarycontactid" width="150" />
|
|
10
|
-
* <cell name="createdon" width="125" />
|
|
11
|
-
* </row>
|
|
12
|
-
* </grid>
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { FetchNode, AttributeNode, LinkEntityNode } from "./nodes";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Column configuration for a single column in the grid
|
|
19
|
-
*/
|
|
20
|
-
export interface LayoutColumn {
|
|
21
|
-
/** Attribute logical name */
|
|
22
|
-
name: string;
|
|
23
|
-
/** Column width in pixels */
|
|
24
|
-
width: number;
|
|
25
|
-
/** Link-entity alias if this column is from a related entity */
|
|
26
|
-
linkEntityAlias?: string;
|
|
27
|
-
/** Whether sorting is disabled for this column */
|
|
28
|
-
disableSorting?: boolean;
|
|
29
|
-
/** Image provider for icon columns */
|
|
30
|
-
imageProviderName?: string;
|
|
31
|
-
/** Original display name from metadata (for UI) */
|
|
32
|
-
displayName?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Full layout configuration for the grid
|
|
37
|
-
*/
|
|
38
|
-
export interface LayoutXmlConfig {
|
|
39
|
-
/** Grid name (usually "resultset") */
|
|
40
|
-
gridName: string;
|
|
41
|
-
/** Entity type code */
|
|
42
|
-
objectTypeCode?: number;
|
|
43
|
-
/** Attribute to navigate to on double-click */
|
|
44
|
-
jumpAttribute?: string;
|
|
45
|
-
/** Row selection enabled */
|
|
46
|
-
enableSelection?: boolean;
|
|
47
|
-
/** Show entity icon */
|
|
48
|
-
showIcon?: boolean;
|
|
49
|
-
/** Enable row preview */
|
|
50
|
-
enablePreview?: boolean;
|
|
51
|
-
/** Primary ID attribute for the row */
|
|
52
|
-
primaryIdAttribute?: string;
|
|
53
|
-
/** Column configurations in display order */
|
|
54
|
-
columns: LayoutColumn[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Default column widths based on attribute type
|
|
59
|
-
*/
|
|
60
|
-
const DEFAULT_WIDTHS: Record<string, number> = {
|
|
61
|
-
// Text types - wider
|
|
62
|
-
String: 200,
|
|
63
|
-
Memo: 250,
|
|
64
|
-
|
|
65
|
-
// Numeric types - narrower
|
|
66
|
-
Integer: 100,
|
|
67
|
-
BigInt: 120,
|
|
68
|
-
Decimal: 120,
|
|
69
|
-
Double: 120,
|
|
70
|
-
Money: 130,
|
|
71
|
-
|
|
72
|
-
// Date/Time
|
|
73
|
-
DateTime: 150,
|
|
74
|
-
|
|
75
|
-
// Lookups - medium
|
|
76
|
-
Lookup: 180,
|
|
77
|
-
Customer: 180,
|
|
78
|
-
Owner: 180,
|
|
79
|
-
|
|
80
|
-
// Option sets - medium
|
|
81
|
-
Picklist: 150,
|
|
82
|
-
State: 100,
|
|
83
|
-
Status: 120,
|
|
84
|
-
MultiSelectPicklist: 200,
|
|
85
|
-
|
|
86
|
-
// Boolean - narrow
|
|
87
|
-
Boolean: 80,
|
|
88
|
-
|
|
89
|
-
// Unique identifier - wide (GUIDs)
|
|
90
|
-
Uniqueidentifier: 280,
|
|
91
|
-
|
|
92
|
-
// Image - narrow
|
|
93
|
-
Image: 60,
|
|
94
|
-
|
|
95
|
-
// File - medium
|
|
96
|
-
File: 150,
|
|
97
|
-
|
|
98
|
-
// Default fallback
|
|
99
|
-
default: 150,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get default width for an attribute type
|
|
104
|
-
*/
|
|
105
|
-
export function getDefaultWidthForType(attributeType?: string): number {
|
|
106
|
-
if (!attributeType) return DEFAULT_WIDTHS.default;
|
|
107
|
-
return DEFAULT_WIDTHS[attributeType] ?? DEFAULT_WIDTHS.default;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Parse a LayoutXML string into a LayoutXmlConfig
|
|
112
|
-
*/
|
|
113
|
-
export function parseLayoutXml(xml: string): LayoutXmlConfig {
|
|
114
|
-
const parser = new DOMParser();
|
|
115
|
-
const doc = parser.parseFromString(xml, "text/xml");
|
|
116
|
-
|
|
117
|
-
// Check for parsing errors
|
|
118
|
-
const parseError = doc.querySelector("parsererror");
|
|
119
|
-
if (parseError) {
|
|
120
|
-
throw new Error(`Invalid LayoutXML: ${parseError.textContent}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const gridElement = doc.querySelector("grid");
|
|
124
|
-
if (!gridElement) {
|
|
125
|
-
throw new Error("Invalid LayoutXML: missing <grid> element");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const rowElement = doc.querySelector("row");
|
|
129
|
-
const cellElements = doc.querySelectorAll("cell");
|
|
130
|
-
|
|
131
|
-
const columns: LayoutColumn[] = [];
|
|
132
|
-
cellElements.forEach((cell) => {
|
|
133
|
-
const name = cell.getAttribute("name");
|
|
134
|
-
const width = cell.getAttribute("width");
|
|
135
|
-
const disableSorting = cell.getAttribute("disableSorting");
|
|
136
|
-
const imageProviderName = cell.getAttribute("imageproviderwebresource");
|
|
137
|
-
|
|
138
|
-
if (name) {
|
|
139
|
-
columns.push({
|
|
140
|
-
name,
|
|
141
|
-
width: width ? parseInt(width, 10) : DEFAULT_WIDTHS.default,
|
|
142
|
-
disableSorting: disableSorting === "1",
|
|
143
|
-
imageProviderName: imageProviderName || undefined,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
gridName: gridElement.getAttribute("name") || "resultset",
|
|
150
|
-
objectTypeCode: gridElement.getAttribute("object")
|
|
151
|
-
? parseInt(gridElement.getAttribute("object")!, 10)
|
|
152
|
-
: undefined,
|
|
153
|
-
jumpAttribute: gridElement.getAttribute("jump") || undefined,
|
|
154
|
-
enableSelection: gridElement.getAttribute("select") === "1",
|
|
155
|
-
showIcon: gridElement.getAttribute("icon") === "1",
|
|
156
|
-
enablePreview: gridElement.getAttribute("preview") === "1",
|
|
157
|
-
primaryIdAttribute: rowElement?.getAttribute("id") || undefined,
|
|
158
|
-
columns,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Generate LayoutXML string from a LayoutXmlConfig
|
|
164
|
-
*/
|
|
165
|
-
export function generateLayoutXml(config: LayoutXmlConfig): string {
|
|
166
|
-
const cells = config.columns
|
|
167
|
-
.map((col) => {
|
|
168
|
-
const attrs: string[] = [`name="${col.name}"`, `width="${col.width}"`];
|
|
169
|
-
if (col.disableSorting) {
|
|
170
|
-
attrs.push('disableSorting="1"');
|
|
171
|
-
}
|
|
172
|
-
if (col.imageProviderName) {
|
|
173
|
-
attrs.push(`imageproviderwebresource="${col.imageProviderName}"`);
|
|
174
|
-
}
|
|
175
|
-
return ` <cell ${attrs.join(" ")} />`;
|
|
176
|
-
})
|
|
177
|
-
.join("\n");
|
|
178
|
-
|
|
179
|
-
const gridAttrs: string[] = [`name="${config.gridName}"`];
|
|
180
|
-
if (config.objectTypeCode !== undefined) {
|
|
181
|
-
gridAttrs.push(`object="${config.objectTypeCode}"`);
|
|
182
|
-
}
|
|
183
|
-
if (config.jumpAttribute) {
|
|
184
|
-
gridAttrs.push(`jump="${config.jumpAttribute}"`);
|
|
185
|
-
}
|
|
186
|
-
gridAttrs.push(`select="${config.enableSelection ? "1" : "0"}"`);
|
|
187
|
-
gridAttrs.push(`icon="${config.showIcon ? "1" : "0"}"`);
|
|
188
|
-
gridAttrs.push(`preview="${config.enablePreview ? "1" : "0"}"`);
|
|
189
|
-
|
|
190
|
-
const rowId = config.primaryIdAttribute || "id";
|
|
191
|
-
|
|
192
|
-
return `<grid ${gridAttrs.join(" ")}>
|
|
193
|
-
<row name="result" id="${rowId}">
|
|
194
|
-
${cells}
|
|
195
|
-
</row>
|
|
196
|
-
</grid>`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Collect all attributes from a FetchXML query into column configs
|
|
201
|
-
* Traverses root entity and all link-entities recursively
|
|
202
|
-
*/
|
|
203
|
-
export function collectAttributesFromFetchXml(
|
|
204
|
-
fetchQuery: FetchNode,
|
|
205
|
-
attributeTypeMap?: Map<string, string>
|
|
206
|
-
): LayoutColumn[] {
|
|
207
|
-
const columns: LayoutColumn[] = [];
|
|
208
|
-
|
|
209
|
-
// Helper to get the column key for an attribute
|
|
210
|
-
const getColumnKey = (attr: AttributeNode, linkAlias?: string): string => {
|
|
211
|
-
// If attribute has an alias, use it
|
|
212
|
-
if (attr.alias) return attr.alias;
|
|
213
|
-
// If from link-entity with alias, prefix with alias
|
|
214
|
-
if (linkAlias) return `${linkAlias}.${attr.name}`;
|
|
215
|
-
// Otherwise just the attribute name
|
|
216
|
-
return attr.name;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Collect from root entity
|
|
220
|
-
if (fetchQuery.entity.attributes) {
|
|
221
|
-
for (const attr of fetchQuery.entity.attributes) {
|
|
222
|
-
const columnKey = getColumnKey(attr);
|
|
223
|
-
const attrType = attributeTypeMap?.get(attr.name);
|
|
224
|
-
columns.push({
|
|
225
|
-
name: columnKey,
|
|
226
|
-
width: getDefaultWidthForType(attrType),
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Recursive function to collect from link-entities
|
|
232
|
-
const collectFromLinkEntity = (link: LinkEntityNode) => {
|
|
233
|
-
// Determine the alias for this link-entity
|
|
234
|
-
const linkAlias = link.alias || link.name;
|
|
235
|
-
|
|
236
|
-
// Collect attributes
|
|
237
|
-
if (link.attributes) {
|
|
238
|
-
for (const attr of link.attributes) {
|
|
239
|
-
const columnKey = getColumnKey(attr, linkAlias);
|
|
240
|
-
const attrType = attributeTypeMap?.get(`${link.name}.${attr.name}`);
|
|
241
|
-
columns.push({
|
|
242
|
-
name: columnKey,
|
|
243
|
-
width: getDefaultWidthForType(attrType),
|
|
244
|
-
linkEntityAlias: linkAlias,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Recurse into nested link-entities
|
|
250
|
-
if (link.links) {
|
|
251
|
-
for (const nestedLink of link.links) {
|
|
252
|
-
collectFromLinkEntity(nestedLink);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// Collect from all link-entities
|
|
258
|
-
if (fetchQuery.entity.links) {
|
|
259
|
-
for (const link of fetchQuery.entity.links) {
|
|
260
|
-
collectFromLinkEntity(link);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return columns;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Generate a LayoutXmlConfig from a FetchXML query
|
|
269
|
-
* Used when no layoutxml exists (custom FetchXML)
|
|
270
|
-
*/
|
|
271
|
-
export function generateLayoutFromFetchXml(
|
|
272
|
-
fetchQuery: FetchNode,
|
|
273
|
-
attributeTypeMap?: Map<string, string>
|
|
274
|
-
): LayoutXmlConfig {
|
|
275
|
-
const entityName = fetchQuery.entity.name;
|
|
276
|
-
const columns = collectAttributesFromFetchXml(fetchQuery, attributeTypeMap);
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
gridName: "resultset",
|
|
280
|
-
jumpAttribute: columns.length > 0 ? columns[0].name : undefined,
|
|
281
|
-
enableSelection: true,
|
|
282
|
-
showIcon: true,
|
|
283
|
-
enablePreview: true,
|
|
284
|
-
primaryIdAttribute: `${entityName}id`,
|
|
285
|
-
columns,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Merge existing layout with FetchXML changes
|
|
291
|
-
* - Keeps existing column widths/order for columns that still exist
|
|
292
|
-
* - Adds new columns from FetchXML at the end
|
|
293
|
-
* - Removes columns no longer in FetchXML
|
|
294
|
-
*/
|
|
295
|
-
export function mergeLayoutWithFetchXml(
|
|
296
|
-
existingConfig: LayoutXmlConfig,
|
|
297
|
-
fetchQuery: FetchNode,
|
|
298
|
-
attributeTypeMap?: Map<string, string>
|
|
299
|
-
): LayoutXmlConfig {
|
|
300
|
-
const fetchColumns = collectAttributesFromFetchXml(fetchQuery, attributeTypeMap);
|
|
301
|
-
const fetchColumnNames = new Set(fetchColumns.map((c) => c.name));
|
|
302
|
-
const existingColumnNames = new Set(existingConfig.columns.map((c) => c.name));
|
|
303
|
-
|
|
304
|
-
// Keep existing columns that are still in FetchXML (preserves order and width)
|
|
305
|
-
const mergedColumns: LayoutColumn[] = existingConfig.columns.filter((col) =>
|
|
306
|
-
fetchColumnNames.has(col.name)
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
// Add new columns from FetchXML that weren't in existing layout
|
|
310
|
-
for (const col of fetchColumns) {
|
|
311
|
-
if (!existingColumnNames.has(col.name)) {
|
|
312
|
-
mergedColumns.push(col);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
...existingConfig,
|
|
318
|
-
columns: mergedColumns,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Check if a LayoutXmlConfig matches the FetchXML attributes
|
|
324
|
-
* Returns true if all FetchXML attributes are represented in the layout
|
|
325
|
-
*/
|
|
326
|
-
export function isLayoutValidForFetchXml(config: LayoutXmlConfig, fetchQuery: FetchNode): boolean {
|
|
327
|
-
const fetchColumns = collectAttributesFromFetchXml(fetchQuery);
|
|
328
|
-
const layoutColumnNames = new Set(config.columns.map((c) => c.name));
|
|
329
|
-
|
|
330
|
-
// Check if all FetchXML attributes are in the layout
|
|
331
|
-
for (const col of fetchColumns) {
|
|
332
|
-
if (!layoutColumnNames.has(col.name)) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Update a single column's width in the config
|
|
342
|
-
*/
|
|
343
|
-
export function updateColumnWidth(
|
|
344
|
-
config: LayoutXmlConfig,
|
|
345
|
-
columnName: string,
|
|
346
|
-
newWidth: number
|
|
347
|
-
): LayoutXmlConfig {
|
|
348
|
-
return {
|
|
349
|
-
...config,
|
|
350
|
-
columns: config.columns.map((col) =>
|
|
351
|
-
col.name === columnName ? { ...col, width: newWidth } : col
|
|
352
|
-
),
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Reorder columns in the config
|
|
358
|
-
*/
|
|
359
|
-
export function reorderColumns(
|
|
360
|
-
config: LayoutXmlConfig,
|
|
361
|
-
fromIndex: number,
|
|
362
|
-
toIndex: number
|
|
363
|
-
): LayoutXmlConfig {
|
|
364
|
-
const columns = [...config.columns];
|
|
365
|
-
const [removed] = columns.splice(fromIndex, 1);
|
|
366
|
-
columns.splice(toIndex, 0, removed);
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
...config,
|
|
370
|
-
columns,
|
|
371
|
-
};
|
|
372
|
-
}
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript type definitions for FetchXML Studio tree nodes
|
|
3
|
-
* Based on FetchXML XSD schema with all supported attributes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type NodeId = string;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Root fetch node - top level query configuration
|
|
10
|
-
*/
|
|
11
|
-
export interface FetchNode {
|
|
12
|
-
id: NodeId;
|
|
13
|
-
type: "fetch";
|
|
14
|
-
entity: EntityNode;
|
|
15
|
-
options: {
|
|
16
|
-
aggregate?: boolean;
|
|
17
|
-
distinct?: boolean;
|
|
18
|
-
top?: number;
|
|
19
|
-
count?: number;
|
|
20
|
-
page?: number;
|
|
21
|
-
pagingCookie?: string;
|
|
22
|
-
returnTotalRecordCount?: boolean;
|
|
23
|
-
noLock?: boolean;
|
|
24
|
-
utcOffset?: number;
|
|
25
|
-
latematerialize?: boolean;
|
|
26
|
-
// Query optimization hints
|
|
27
|
-
hints?: {
|
|
28
|
-
forceOrder?: boolean;
|
|
29
|
-
disableRowGoal?: boolean;
|
|
30
|
-
enableOptimizerHotfixes?: boolean;
|
|
31
|
-
loopJoin?: boolean;
|
|
32
|
-
mergeJoin?: boolean;
|
|
33
|
-
hashJoin?: boolean;
|
|
34
|
-
noPerformanceSpool?: boolean;
|
|
35
|
-
enableHistAmendmentForAscKeys?: boolean;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Entity node - the root or linked table being queried
|
|
42
|
-
*/
|
|
43
|
-
export interface EntityNode {
|
|
44
|
-
id: NodeId;
|
|
45
|
-
type: "entity";
|
|
46
|
-
name: string; // logical name (e.g., 'account', 'contact')
|
|
47
|
-
enablePrefiltering?: boolean;
|
|
48
|
-
prefilterParameterName?: string;
|
|
49
|
-
allAttributes?: AllAttributesNode;
|
|
50
|
-
attributes: AttributeNode[];
|
|
51
|
-
orders: OrderNode[];
|
|
52
|
-
filters: FilterNode[];
|
|
53
|
-
links: LinkEntityNode[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* All-Attributes node - select all columns from entity
|
|
58
|
-
*/
|
|
59
|
-
export interface AllAttributesNode {
|
|
60
|
-
id: NodeId;
|
|
61
|
-
type: "all-attributes";
|
|
62
|
-
enabled: boolean; // toggle to include/exclude all attributes
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Attribute node - individual column selection
|
|
67
|
-
*/
|
|
68
|
-
export interface AttributeNode {
|
|
69
|
-
id: NodeId;
|
|
70
|
-
type: "attribute";
|
|
71
|
-
name: string; // logical attribute name
|
|
72
|
-
alias?: string;
|
|
73
|
-
groupby?: boolean;
|
|
74
|
-
aggregate?: "sum" | "count" | "countcolumn" | "min" | "max" | "avg" | "rowaggregate";
|
|
75
|
-
usertimezone?: boolean;
|
|
76
|
-
dategrouping?: "day" | "week" | "month" | "quarter" | "year" | "fiscal-period" | "fiscal-year";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Order node - sorting specification
|
|
81
|
-
*/
|
|
82
|
-
export interface OrderNode {
|
|
83
|
-
id: NodeId;
|
|
84
|
-
type: "order";
|
|
85
|
-
attribute: string; // logical attribute name
|
|
86
|
-
descending?: boolean;
|
|
87
|
-
entityname?: string; // for link-entity attributes, references the link alias
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Filter node - logical grouping of conditions
|
|
92
|
-
*/
|
|
93
|
-
export interface FilterNode {
|
|
94
|
-
id: NodeId;
|
|
95
|
-
type: "filter";
|
|
96
|
-
conjunction: "and" | "or"; // filter type
|
|
97
|
-
hint?: "union"; // optimization hint for multi-table filters
|
|
98
|
-
conditions: ConditionNode[];
|
|
99
|
-
subfilters: FilterNode[]; // nested filters
|
|
100
|
-
links: LinkEntityNode[]; // link-entities inside filter (for any/not any/all/not all)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Condition node - individual filter criteria
|
|
105
|
-
*/
|
|
106
|
-
export interface ConditionNode {
|
|
107
|
-
id: NodeId;
|
|
108
|
-
type: "condition";
|
|
109
|
-
attribute: string; // logical attribute name
|
|
110
|
-
operator: OperatorType;
|
|
111
|
-
value?: string | number | boolean | (string | number)[] | null; // arrays for in/not-in operators
|
|
112
|
-
aggregate?: "sum" | "count" | "countcolumn" | "min" | "max" | "avg"; // for aggregate filtering
|
|
113
|
-
entityname?: string; // for link-entity filters, references the entity/link alias
|
|
114
|
-
valueof?: string; // for cross-table column comparisons (references another attribute)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Link-Entity node - join to related table
|
|
119
|
-
*/
|
|
120
|
-
export interface LinkEntityNode {
|
|
121
|
-
id: NodeId;
|
|
122
|
-
type: "link-entity";
|
|
123
|
-
name: string; // related entity logical name
|
|
124
|
-
from: string; // attribute on related entity
|
|
125
|
-
to: string; // attribute on current entity
|
|
126
|
-
linkType: LinkType;
|
|
127
|
-
alias?: string;
|
|
128
|
-
intersect?: boolean; // for N:N relationships
|
|
129
|
-
visible?: boolean; // include in query results
|
|
130
|
-
relationshipType?: "1N" | "N1" | "NN"; // metadata hint for UI
|
|
131
|
-
allAttributes?: AllAttributesNode;
|
|
132
|
-
attributes: AttributeNode[];
|
|
133
|
-
orders: OrderNode[];
|
|
134
|
-
filters: FilterNode[];
|
|
135
|
-
links: LinkEntityNode[]; // nested link-entities (max 15 total depth)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Link types supported by FetchXML
|
|
140
|
-
*/
|
|
141
|
-
export type LinkType =
|
|
142
|
-
| "inner" // Inner join (default)
|
|
143
|
-
| "outer" // Left outer join
|
|
144
|
-
| "any" // Exists with ANY match in related
|
|
145
|
-
| "not any" // NOT EXISTS with any match
|
|
146
|
-
| "all" // ALL records match
|
|
147
|
-
| "not all" // NOT ALL records match
|
|
148
|
-
| "exists" // EXISTS (semi-join)
|
|
149
|
-
| "in" // IN (similar to exists)
|
|
150
|
-
| "matchfirstrowusingcrossapply"; // CROSS APPLY for first match
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* FetchXML operators (60+ supported)
|
|
154
|
-
* Applicability depends on attribute type
|
|
155
|
-
*/
|
|
156
|
-
export type OperatorType =
|
|
157
|
-
// Comparison operators (all types)
|
|
158
|
-
| "eq"
|
|
159
|
-
| "ne"
|
|
160
|
-
| "gt"
|
|
161
|
-
| "ge"
|
|
162
|
-
| "lt"
|
|
163
|
-
| "le"
|
|
164
|
-
// Null checks (all types)
|
|
165
|
-
| "null"
|
|
166
|
-
| "not-null"
|
|
167
|
-
// String operators
|
|
168
|
-
| "like"
|
|
169
|
-
| "not-like"
|
|
170
|
-
| "begins-with"
|
|
171
|
-
| "not-begin-with"
|
|
172
|
-
| "ends-with"
|
|
173
|
-
| "not-end-with"
|
|
174
|
-
// Collection operators
|
|
175
|
-
| "in"
|
|
176
|
-
| "not-in"
|
|
177
|
-
| "between"
|
|
178
|
-
| "not-between"
|
|
179
|
-
| "contain-values"
|
|
180
|
-
| "not-contain-values" // for multi-select picklist
|
|
181
|
-
// Date relative operators
|
|
182
|
-
| "yesterday"
|
|
183
|
-
| "today"
|
|
184
|
-
| "tomorrow"
|
|
185
|
-
| "last-seven-days"
|
|
186
|
-
| "last-week"
|
|
187
|
-
| "this-week"
|
|
188
|
-
| "next-week"
|
|
189
|
-
| "next-seven-days"
|
|
190
|
-
| "last-month"
|
|
191
|
-
| "this-month"
|
|
192
|
-
| "next-month"
|
|
193
|
-
| "last-year"
|
|
194
|
-
| "this-year"
|
|
195
|
-
| "next-year"
|
|
196
|
-
| "on"
|
|
197
|
-
| "on-or-before"
|
|
198
|
-
| "on-or-after"
|
|
199
|
-
// Date range operators (require integer value for 'x')
|
|
200
|
-
| "last-x-hours"
|
|
201
|
-
| "next-x-hours"
|
|
202
|
-
| "last-x-days"
|
|
203
|
-
| "next-x-days"
|
|
204
|
-
| "last-x-weeks"
|
|
205
|
-
| "next-x-weeks"
|
|
206
|
-
| "last-x-months"
|
|
207
|
-
| "next-x-months"
|
|
208
|
-
| "last-x-years"
|
|
209
|
-
| "next-x-years"
|
|
210
|
-
| "olderthan-x-minutes"
|
|
211
|
-
| "olderthan-x-hours"
|
|
212
|
-
| "olderthan-x-days"
|
|
213
|
-
| "olderthan-x-weeks"
|
|
214
|
-
| "olderthan-x-months"
|
|
215
|
-
| "olderthan-x-years"
|
|
216
|
-
// Fiscal period operators
|
|
217
|
-
| "this-fiscal-year"
|
|
218
|
-
| "this-fiscal-period"
|
|
219
|
-
| "next-fiscal-year"
|
|
220
|
-
| "next-fiscal-period"
|
|
221
|
-
| "last-fiscal-year"
|
|
222
|
-
| "last-fiscal-period"
|
|
223
|
-
| "last-x-fiscal-years"
|
|
224
|
-
| "last-x-fiscal-periods"
|
|
225
|
-
| "next-x-fiscal-years"
|
|
226
|
-
| "next-x-fiscal-periods"
|
|
227
|
-
| "in-fiscal-year"
|
|
228
|
-
| "in-fiscal-period"
|
|
229
|
-
| "in-fiscal-period-and-year"
|
|
230
|
-
| "in-or-before-fiscal-period-and-year"
|
|
231
|
-
| "in-or-after-fiscal-period-and-year"
|
|
232
|
-
// User/Business unit operators (for Lookup to systemuser/businessunit)
|
|
233
|
-
| "eq-userid"
|
|
234
|
-
| "ne-userid"
|
|
235
|
-
| "eq-userteams"
|
|
236
|
-
| "eq-useroruserteams"
|
|
237
|
-
| "eq-useroruserhierarchy"
|
|
238
|
-
| "eq-useroruserhierarchyandteams"
|
|
239
|
-
| "eq-businessid"
|
|
240
|
-
| "ne-businessid"
|
|
241
|
-
| "eq-userlanguage"
|
|
242
|
-
// Hierarchy operators (for hierarchical lookups)
|
|
243
|
-
| "under"
|
|
244
|
-
| "eq-or-under"
|
|
245
|
-
| "not-under"
|
|
246
|
-
| "above"
|
|
247
|
-
| "eq-or-above";
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Value node - for operators that accept multiple values (e.g., 'in')
|
|
251
|
-
* Not a tree node, but used in FetchXML generation
|
|
252
|
-
*/
|
|
253
|
-
export interface ValueNode {
|
|
254
|
-
value: string | number | boolean;
|
|
255
|
-
}
|