@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,18 +0,0 @@
1
- /**
2
- * Display settings types and defaults
3
- */
4
-
5
- export type ValueDisplayMode = "formatted" | "raw" | "both";
6
-
7
- export interface DisplaySettings {
8
- /** Show logical names in column headers instead of display names */
9
- useLogicalNames: boolean;
10
- /** How to display cell values: formatted, raw, or both */
11
- valueDisplayMode: ValueDisplayMode;
12
- }
13
-
14
- /** Default display settings */
15
- export const defaultDisplaySettings: DisplaySettings = {
16
- useLogicalNames: false,
17
- valueDisplayMode: "formatted",
18
- };
@@ -1,346 +0,0 @@
1
- /**
2
- * Pure functions to generate FetchXML from tree structure
3
- * Follows Dataverse FetchXML semantics
4
- */
5
-
6
- import type {
7
- FetchNode,
8
- EntityNode,
9
- AttributeNode,
10
- AllAttributesNode,
11
- OrderNode,
12
- FilterNode,
13
- ConditionNode,
14
- LinkEntityNode,
15
- } from "./nodes";
16
- import { operatorRequiresValue } from "./operators";
17
-
18
- /**
19
- * Convert a value to string for FetchXML, avoiding scientific notation for numbers
20
- */
21
- function formatValueForFetchXml(val: unknown): string {
22
- if (typeof val === "number") {
23
- // Avoid scientific notation (1e-12) by using toFixed with sufficient precision
24
- // Then remove trailing zeros
25
- if (Math.abs(val) < 1e-10 && val !== 0) {
26
- // Very small numbers - use high precision
27
- return val.toFixed(20).replace(/\.?0+$/, "");
28
- } else if (Number.isInteger(val)) {
29
- // Integer - no decimal point needed
30
- return val.toString();
31
- } else {
32
- // Decimal - preserve precision but remove trailing zeros
33
- return val.toFixed(10).replace(/\.?0+$/, "");
34
- }
35
- }
36
- return String(val);
37
- }
38
-
39
- /**
40
- * Generate complete FetchXML from FetchNode
41
- */
42
- export function generateFetchXml(fetchNode: FetchNode): string {
43
- const lines: string[] = [];
44
-
45
- // Build fetch element with options
46
- const fetchAttrs: string[] = [];
47
- if (fetchNode.options.aggregate) fetchAttrs.push('aggregate="true"');
48
- if (fetchNode.options.distinct) fetchAttrs.push('distinct="true"');
49
- if (fetchNode.options.top !== undefined) fetchAttrs.push(`top="${fetchNode.options.top}"`);
50
- if (fetchNode.options.count !== undefined) fetchAttrs.push(`count="${fetchNode.options.count}"`);
51
- if (fetchNode.options.page !== undefined) fetchAttrs.push(`page="${fetchNode.options.page}"`);
52
- if (fetchNode.options.returnTotalRecordCount) fetchAttrs.push('returntotalrecordcount="true"');
53
- if (fetchNode.options.noLock) fetchAttrs.push('no-lock="true"');
54
- if (fetchNode.options.utcOffset !== undefined)
55
- fetchAttrs.push(`utc-offset="${fetchNode.options.utcOffset}"`);
56
- if (fetchNode.options.pagingCookie)
57
- fetchAttrs.push(`paging-cookie="${escapeXml(fetchNode.options.pagingCookie)}"`);
58
-
59
- const fetchAttrStr = fetchAttrs.length > 0 ? " " + fetchAttrs.join(" ") : "";
60
- lines.push(`<fetch${fetchAttrStr}>`);
61
-
62
- // Generate entity
63
- lines.push(...generateEntity(fetchNode.entity, 1));
64
-
65
- lines.push("</fetch>");
66
-
67
- return lines.join("\n");
68
- }
69
-
70
- /**
71
- * Generate entity element
72
- */
73
- function generateEntity(entity: EntityNode, indent: number): string[] {
74
- const lines: string[] = [];
75
- const spaces = " ".repeat(indent);
76
-
77
- lines.push(`${spaces}<entity name="${entity.name}">`);
78
-
79
- // All-attributes (single node, not array)
80
- if (entity.allAttributes?.enabled) {
81
- lines.push(...generateAllAttributes(entity.allAttributes, indent + 1));
82
- }
83
-
84
- // CRITICAL: Always include the primary ID attribute for row selection to work
85
- // The primary ID is {entityname}id (e.g., accountid, contactid)
86
- const primaryIdAttrName = `${entity.name}id`;
87
- const hasPrimaryId =
88
- entity.attributes.some((attr) => attr.name === primaryIdAttrName) ||
89
- entity.allAttributes?.enabled;
90
-
91
- if (!hasPrimaryId) {
92
- // Inject primary ID attribute at the beginning
93
- const primaryIdSpaces = " ".repeat(indent + 1);
94
- lines.push(`${primaryIdSpaces}<attribute name="${primaryIdAttrName}" />`);
95
- }
96
-
97
- // Attributes
98
- entity.attributes.forEach((attr) => {
99
- lines.push(...generateAttribute(attr, indent + 1));
100
- });
101
-
102
- // Orders
103
- entity.orders?.forEach((order) => {
104
- lines.push(...generateOrder(order, indent + 1));
105
- });
106
-
107
- // Filters
108
- entity.filters?.forEach((filter) => {
109
- lines.push(...generateFilter(filter, indent + 1));
110
- });
111
-
112
- // Link-entities
113
- entity.links?.forEach((link) => {
114
- lines.push(...generateLinkEntity(link, indent + 1));
115
- });
116
-
117
- lines.push(`${spaces}</entity>`);
118
-
119
- return lines;
120
- }
121
-
122
- /**
123
- * Generate all-attributes element
124
- */
125
- function generateAllAttributes(_allAttr: AllAttributesNode, indent: number): string[] {
126
- const spaces = " ".repeat(indent);
127
- return [`${spaces}<all-attributes />`];
128
- }
129
-
130
- /**
131
- * Generate attribute element
132
- */
133
- function generateAttribute(attr: AttributeNode, indent: number): string[] {
134
- const spaces = " ".repeat(indent);
135
- const attrs: string[] = [`name="${attr.name}"`];
136
-
137
- if (attr.alias) attrs.push(`alias="${attr.alias}"`);
138
- if (attr.aggregate) attrs.push(`aggregate="${attr.aggregate}"`);
139
- if (attr.groupby) attrs.push('groupby="true"');
140
- if (attr.dategrouping) attrs.push(`dategrouping="${attr.dategrouping}"`);
141
- if (attr.usertimezone !== undefined) attrs.push(`usertimezone="${attr.usertimezone}"`);
142
-
143
- return [`${spaces}<attribute ${attrs.join(" ")} />`];
144
- }
145
-
146
- /**
147
- * Generate order element
148
- */
149
- function generateOrder(order: OrderNode, indent: number): string[] {
150
- const spaces = " ".repeat(indent);
151
- const attrs: string[] = [`attribute="${order.attribute}"`];
152
-
153
- if (order.descending) attrs.push('descending="true"');
154
- if (order.entityname) attrs.push(`entityname="${order.entityname}"`);
155
-
156
- return [`${spaces}<order ${attrs.join(" ")} />`];
157
- }
158
-
159
- /**
160
- * Generate filter element (recursive for subfilters)
161
- */
162
- function generateFilter(filter: FilterNode, indent: number): string[] {
163
- const lines: string[] = [];
164
- const spaces = " ".repeat(indent);
165
-
166
- const attrs: string[] = [`type="${filter.conjunction}"`];
167
- if (filter.hint) attrs.push(`hint="${filter.hint}"`);
168
-
169
- lines.push(`${spaces}<filter ${attrs.join(" ")}>`);
170
-
171
- // Conditions
172
- filter.conditions?.forEach((condition) => {
173
- lines.push(...generateCondition(condition, indent + 1));
174
- });
175
-
176
- // Subfilters (recursive)
177
- filter.subfilters?.forEach((subfilter) => {
178
- lines.push(...generateFilter(subfilter, indent + 1));
179
- });
180
-
181
- // Link-entities inside filter (for any/not any/all/not all)
182
- filter.links?.forEach((link) => {
183
- lines.push(...generateLinkEntity(link, indent + 1));
184
- });
185
-
186
- lines.push(`${spaces}</filter>`);
187
-
188
- return lines;
189
- }
190
-
191
- /**
192
- * Generate condition element
193
- */
194
- function generateCondition(condition: ConditionNode, indent: number): string[] {
195
- const lines: string[] = [];
196
- const spaces = " ".repeat(indent);
197
-
198
- const attrs: string[] = [
199
- `attribute="${condition.attribute}"`,
200
- `operator="${condition.operator}"`,
201
- ];
202
-
203
- if (condition.entityname) attrs.push(`entityname="${condition.entityname}"`);
204
- if (condition.aggregate) attrs.push(`aggregate="${condition.aggregate}"`);
205
- // Note: ConditionNode doesn't have alias in our type definition
206
- if (condition.valueof) attrs.push(`valueof="${condition.valueof}"`);
207
-
208
- // Use the centralized operator logic to determine if value is needed
209
- const needsValue = operatorRequiresValue(condition.operator);
210
-
211
- if (!needsValue) {
212
- // Self-closing tag for operators that don't need values (null, eq-userid, etc.)
213
- return [`${spaces}<condition ${attrs.join(" ")} />`];
214
- }
215
-
216
- // Operators that need value attribute
217
- if (condition.value !== undefined && condition.value !== null) {
218
- // Handle array values (for 'in', 'not-in', 'contain-values', 'between', etc.)
219
- if (Array.isArray(condition.value)) {
220
- lines.push(`${spaces}<condition ${attrs.join(" ")}>`);
221
- condition.value.forEach((val) => {
222
- if (val !== undefined && val !== null) {
223
- lines.push(`${spaces} <value>${escapeXml(formatValueForFetchXml(val))}</value>`);
224
- }
225
- });
226
- lines.push(`${spaces}</condition>`);
227
- } else {
228
- // Single value attribute
229
- attrs.push(`value="${escapeXml(formatValueForFetchXml(condition.value))}"`);
230
- lines.push(`${spaces}<condition ${attrs.join(" ")} />`);
231
- }
232
- } else {
233
- // No value provided but operator needs one - still generate with empty value
234
- lines.push(`${spaces}<condition ${attrs.join(" ")} />`);
235
- }
236
-
237
- return lines;
238
- }
239
-
240
- /**
241
- * Generate link-entity element (recursive)
242
- */
243
- function generateLinkEntity(link: LinkEntityNode, indent: number): string[] {
244
- const lines: string[] = [];
245
- const spaces = " ".repeat(indent);
246
-
247
- const attrs: string[] = [`name="${link.name}"`, `from="${link.from}"`, `to="${link.to}"`];
248
-
249
- if (link.alias) attrs.push(`alias="${link.alias}"`);
250
- if (link.linkType !== "inner") attrs.push(`link-type="${link.linkType}"`);
251
- if (link.intersect) attrs.push('intersect="true"');
252
- if (link.visible !== undefined) attrs.push(`visible="${link.visible}"`);
253
-
254
- lines.push(`${spaces}<link-entity ${attrs.join(" ")}>`);
255
-
256
- // All-attributes (single node, not array)
257
- if (link.allAttributes?.enabled) {
258
- lines.push(...generateAllAttributes(link.allAttributes, indent + 1));
259
- }
260
-
261
- // Attributes
262
- link.attributes?.forEach((attr) => {
263
- lines.push(...generateAttribute(attr, indent + 1));
264
- });
265
-
266
- // Orders
267
- link.orders?.forEach((order) => {
268
- lines.push(...generateOrder(order, indent + 1));
269
- });
270
-
271
- // Filters
272
- link.filters?.forEach((filter) => {
273
- lines.push(...generateFilter(filter, indent + 1));
274
- });
275
-
276
- // Nested link-entities (recursive)
277
- link.links?.forEach((nestedLink) => {
278
- lines.push(...generateLinkEntity(nestedLink, indent + 1));
279
- });
280
-
281
- lines.push(`${spaces}</link-entity>`);
282
-
283
- return lines;
284
- }
285
-
286
- /**
287
- * Escape XML special characters
288
- */
289
- function escapeXml(str: string): string {
290
- return str
291
- .replace(/&/g, "&amp;")
292
- .replace(/</g, "&lt;")
293
- .replace(/>/g, "&gt;")
294
- .replace(/"/g, "&quot;")
295
- .replace(/'/g, "&apos;");
296
- }
297
-
298
- /**
299
- * Modify FetchXML string to add/update paging parameters
300
- * Used for fetching subsequent pages during Retrieve All or infinite scroll
301
- *
302
- * Per Dataverse requirements:
303
- * - page: Page number (1-based)
304
- * - paging-cookie: XML cookie from previous response, must be XML-entity encoded
305
- * - count: Page size (records per page)
306
- */
307
- export function addPagingToFetchXml(
308
- fetchXml: string,
309
- page: number,
310
- pagingCookie?: string,
311
- count?: number
312
- ): string {
313
- // Parse the fetch element attributes
314
- const fetchMatch = fetchXml.match(/<fetch([^>]*)>/);
315
- if (!fetchMatch) return fetchXml;
316
-
317
- let fetchAttrs = fetchMatch[1];
318
-
319
- // Remove existing page, paging-cookie, count attributes
320
- // Note: paging-cookie can contain encoded quotes, so we need a more robust pattern
321
- fetchAttrs = fetchAttrs
322
- .replace(/\s*page="[^"]*"/g, "")
323
- .replace(/\s*paging-cookie="[^"]*"/g, "") // This works because the value is already XML-encoded (no raw quotes)
324
- .replace(/\s*count="[^"]*"/g, "");
325
-
326
- // Add new paging attributes
327
- fetchAttrs = fetchAttrs.trim();
328
-
329
- // Always add page number (even page 1 for explicit paging requests)
330
- fetchAttrs += ` page="${page}"`;
331
-
332
- // Add paging cookie if provided (required for reliable paging after page 1)
333
- if (pagingCookie) {
334
- // The paging cookie needs to be XML-escaped when inserted as an attribute value
335
- // Dataverse returns it as raw XML, we need to encode it for the attribute
336
- fetchAttrs += ` paging-cookie="${escapeXml(pagingCookie)}"`;
337
- }
338
-
339
- // Add count (page size) if specified
340
- if (count !== undefined) {
341
- fetchAttrs += ` count="${count}"`;
342
- }
343
-
344
- // Reconstruct the fetch element
345
- return fetchXml.replace(/<fetch[^>]*>/, `<fetch${fetchAttrs ? " " + fetchAttrs : ""}>`);
346
- }