@mohsinonxrm/pptb-fetchxml-studio 1.4.0 → 1.4.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.
- package/CHANGELOG.md +1240 -441
- package/LICENSE +661 -661
- package/README.md +432 -362
- package/SECURITY.md +13 -13
- package/dist/assets/index-DtvfGsXm.js +109 -0
- package/{icons → dist/icons}/icon.svg +12 -12
- package/dist/index.html +13 -13
- package/index.html +70 -70
- package/package.json +106 -98
- package/pptb.config.json +46 -46
- package/dist/assets/index-BoFrKby3.js +0 -107
- package/icons/icon-insider.svg +0 -16
- package/npm-shrinkwrap.json +0 -6362
package/CHANGELOG.md
CHANGED
|
@@ -1,441 +1,1240 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to FetchXML Studio will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
## [1.4.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- **
|
|
116
|
-
- **
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
- **
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
- `
|
|
177
|
-
- `
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
- **
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
- **
|
|
201
|
-
- **
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
- **
|
|
238
|
-
- **
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
- `
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
-
|
|
282
|
-
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
- `
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
-
|
|
293
|
-
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
###
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
-
|
|
310
|
-
-
|
|
311
|
-
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
- Tree
|
|
339
|
-
-
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
-
|
|
355
|
-
-
|
|
356
|
-
-
|
|
357
|
-
-
|
|
358
|
-
-
|
|
359
|
-
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
-
|
|
364
|
-
-
|
|
365
|
-
-
|
|
366
|
-
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
387
|
-
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
-
|
|
391
|
-
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
-
|
|
398
|
-
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
-
|
|
407
|
-
-
|
|
408
|
-
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
- `
|
|
418
|
-
-
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
-
|
|
422
|
-
- `
|
|
423
|
-
- `
|
|
424
|
-
- `
|
|
425
|
-
- `
|
|
426
|
-
- `
|
|
427
|
-
-
|
|
428
|
-
-
|
|
429
|
-
-
|
|
430
|
-
- `
|
|
431
|
-
- `
|
|
432
|
-
-
|
|
433
|
-
-
|
|
434
|
-
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
- `
|
|
439
|
-
- `
|
|
440
|
-
-
|
|
441
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to FetchXML Studio will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.4.2] - 2026-10-17
|
|
11
|
+
|
|
12
|
+
The v1.4.2 milestone: the features other users asked for, a results grid that works from the query, saved views that behave as documented, operators that follow the docs, and the CI/CD and release setup shared with Command Bar Studio and Dataverse Request Studio. It was iterated through `1.4.2-beta.1` to `1.4.2-beta.7` (detailed below). Behaviour follows Microsoft Learn and the live operator matrix. Valid FetchXML is never refused, and a Dataverse error is shown as it is.
|
|
13
|
+
|
|
14
|
+
### ✨ Added
|
|
15
|
+
|
|
16
|
+
- **Copy results to the clipboard (#50).**
|
|
17
|
+
- Right-click a cell to copy its value, its raw value (GUID, number, ISO date or option value), the row, the selected rows or the column.
|
|
18
|
+
- **Ctrl+C** copies the selected rows, or the focused cell.
|
|
19
|
+
- The command bar's **Copy** menu copies the selected or all loaded rows, as shown or raw.
|
|
20
|
+
- Rows copy as tab-separated values with headers, so they paste into Excel as cells.
|
|
21
|
+
- **Column filters that write FetchXML (#23).**
|
|
22
|
+
- Clicking a column header opens a menu: **Sort**, **Filter by…** and **Clear filter**. Shift+click still adds the column to a multi-column sort.
|
|
23
|
+
- **Filter by…** uses the condition editor's operators and value pickers, adds a `<condition>` and runs the query again, so the filter covers every record.
|
|
24
|
+
- Active filters show as removable tags above the grid.
|
|
25
|
+
- Column filters are kept in their own `<filter type="and">`, apart from the conditions you built. Link-entity columns use `entityname`.
|
|
26
|
+
- **Quick search over the loaded rows (#23).** It filters the loaded rows as you type, in any column, using the values as shown, and highlights matches.
|
|
27
|
+
- **Operator help (#95).** The Operator info popover explains the selected operator and links to its doc page: the hierarchy operators, the hierarchy security operators, Contains Values and Equals User Language.
|
|
28
|
+
|
|
29
|
+
### 🔧 Fixed
|
|
30
|
+
|
|
31
|
+
- **Results grid columns come from the query (#91).** They used to come from the first row, which Dataverse returns without its null values.
|
|
32
|
+
- Lookups that are empty in row 1 now show their values.
|
|
33
|
+
- `<all-attributes />` columns from every loaded page are shown.
|
|
34
|
+
- `matchfirstrowusingcrossapply` columns appear and can be sorted and filtered.
|
|
35
|
+
- The local Excel export has exactly the grid's columns.
|
|
36
|
+
- **Saved views (#93).**
|
|
37
|
+
- An unmodified loaded view runs by its view ID, so Retrieve All pages through it.
|
|
38
|
+
- Personal views shared with you appear in Load View.
|
|
39
|
+
- Every type of view is listed, each labelled.
|
|
40
|
+
- Saved layoutxml uses the documented `jump`, `object` and row `id`.
|
|
41
|
+
- System views are checked with `ValidateSavedQuery`, and views that aren't customizable can't be overwritten.
|
|
42
|
+
- **Save and Publish** publishes the view's entity, the documented `PublishXml` form. The `<savedqueries>` node it used to send isn't in the publish request schema. This also publishes any other pending customizations of that entity (#93 h).
|
|
43
|
+
- Load View reports views it can't load or had to change.
|
|
44
|
+
- **Operators per attribute type (#95).** Memo attributes gain Equals, Not Equals, In and Not In, as do choices (multi-select), which use the option picker. All are documented, verified live, or both. No operator was removed.
|
|
45
|
+
- **Tree (#24, #25).**
|
|
46
|
+
- Selecting a node no longer scrolls the tree to the bottom.
|
|
47
|
+
- A node added from an Add menu is selected and scrolled into view.
|
|
48
|
+
- **Header sort for aggregate and aliased link-entity columns (#92)** was confirmed fixed by #114, and is now covered by tests.
|
|
49
|
+
|
|
50
|
+
### 🏗️ Technical
|
|
51
|
+
|
|
52
|
+
- **CI/CD and releases (#130).**
|
|
53
|
+
- CI runs typecheck, lint, tests, `pptb-validate`, the build and a package-contents check on every PR and on pushes to `dev`/`main`.
|
|
54
|
+
- Releases come from `v*` tags: npm trusted publishing with provenance, and GitHub Releases with generated notes (see `RELEASING.md`).
|
|
55
|
+
- Prettier config, `.gitattributes` (LF), `engines.node >=22`.
|
|
56
|
+
- **Security (#77).** `tmp` was updated to 0.2.7, which clears the high-severity runtime `npm audit` finding.
|
|
57
|
+
- **Tests.** 637 tests, up from 507. New suites:
|
|
58
|
+
- copy (`copyResults`);
|
|
59
|
+
- column filters (`columnFilters`);
|
|
60
|
+
- search highlighting;
|
|
61
|
+
- the operator matrix;
|
|
62
|
+
- grid columns (`resultColumns`);
|
|
63
|
+
- saved views (`savedViews`) and layoutxml.
|
|
64
|
+
|
|
65
|
+
## [1.4.2-beta.7] - 2026-10-17
|
|
66
|
+
|
|
67
|
+
### 🏗️ Technical
|
|
68
|
+
|
|
69
|
+
- **The shared CI/CD and release standard, same as Command Bar Studio and Dataverse Request Studio (#130).**
|
|
70
|
+
- **CI** (`ci.yml`) runs on every PR and on pushes to `dev`/`main`: typecheck, lint, tests, `pptb-validate`, the build, then `check-pack.mjs`. That last step checks the package contains the required files (including `pptb.config.json` and `dist/icons/icon.svg`), contains no `src/`, `.map` or `.github/` files, and stays within a size budget of 3 MB packed / 10 MB unpacked.
|
|
71
|
+
- **Releases** (`release.yml`) come from `v*` tags:
|
|
72
|
+
- the tag must match `package.json`;
|
|
73
|
+
- npm publishing uses trusted publishing with provenance, and `-beta.N` versions go to the `beta` dist-tag;
|
|
74
|
+
- the GitHub Release has generated notes, and betas are marked as pre-releases.
|
|
75
|
+
- See `RELEASING.md`.
|
|
76
|
+
- **Repository tooling:**
|
|
77
|
+
- Added Prettier (`.prettierrc`, `format`/`format:check`), `.gitattributes` (LF), a `typecheck` script (`tsc -b`) and `engines.node >=22`.
|
|
78
|
+
- Removed the `coverage` and `finalize-package` scripts and `@vitest/coverage-v8`.
|
|
79
|
+
- Shorter PR template; the issue forms are trimmed to Bug and Feature.
|
|
80
|
+
- **`tmp` updated to 0.2.7 in the lockfile, which clears the high-severity runtime `npm audit` finding (#77).** It wasn't exploitable in the tool: exceljs's browser build doesn't include tmp. The minimatch and brace-expansion findings were already fixed.
|
|
81
|
+
|
|
82
|
+
## [1.4.2-beta.6] - 2026-10-17
|
|
83
|
+
|
|
84
|
+
### 🔧 Fixed
|
|
85
|
+
|
|
86
|
+
- **Saved views (#93).**
|
|
87
|
+
- **An unmodified loaded view runs by its view ID again**, so Retrieve All pages through it with `@odata.nextLink`. The stored FetchXML is now compared in the tree's generated form, so default attributes such as `descending="false"` no longer cause a mismatch. The comparison is exact; it used to strip whitespace, which hid edits inside attribute values.
|
|
88
|
+
- **Personal views shared with you appear in Load View.** Before, only views owned by you or your teams were listed. Dataverse applies access rights when reading `userquery`, per Saved queries on Microsoft Learn.
|
|
89
|
+
- **Load View lists every type of view**, each labelled. Public views come first (default on top), then Advanced find, Associated, Quick find, Lookup and the others. Overwriting keeps the view's query type; Save As creates a public view.
|
|
90
|
+
- **Saved layoutxml now follows the documented form.**
|
|
91
|
+
- `object` (the entity type code) and the row `id` (the primary key) come from metadata.
|
|
92
|
+
- `jump` is the primary name attribute; it used to be the first column, which could be a lookup.
|
|
93
|
+
- A loaded view keeps its own `jump` and `imageproviderfunctionname`.
|
|
94
|
+
- A query without a column layout gets one generated, instead of an empty layoutxml.
|
|
95
|
+
- Attribute values are escaped.
|
|
96
|
+
- **Save View dialog:**
|
|
97
|
+
- High and critical performance messages show as errors (saving still isn't blocked).
|
|
98
|
+
- A validation call that fails now says so, instead of showing "0 validation messages".
|
|
99
|
+
- "FetchXML is valid" now reads "No performance issues found".
|
|
100
|
+
- **System views that aren't customizable** (`iscustomizable` false) can't be overwritten: the dialog explains this and points to Save As.
|
|
101
|
+
- **Load View reports problems:** an error when a view's FetchXML can't be loaded (it used to fail silently), and a warning listing what the tree had to change.
|
|
102
|
+
|
|
103
|
+
### 🏗️ Technical
|
|
104
|
+
|
|
105
|
+
- System views are checked with `ValidateSavedQuery` before they're saved. It's bound to the `savedqueries` collection (`savedqueries/Microsoft.Dynamics.CRM.ValidateSavedQuery`, per the Web API reference); it used to be called unbound, and in fact was never called. A rejection shows in the existing Save failed bar. There's no `userquery` binding, so personal views aren't validated this way.
|
|
106
|
+
- New `model/savedViews.ts` (`isUnmodifiedView`, query type labels and order, managed property reading, Load View notices) and `buildViewLayoutXml` in `layoutxml.ts`, with tests.
|
|
107
|
+
|
|
108
|
+
## [1.4.2-beta.5] - 2026-10-17
|
|
109
|
+
|
|
110
|
+
### 🔧 Fixed
|
|
111
|
+
|
|
112
|
+
- **Grid columns come from the query, not from the first row (#91).** Dataverse leaves null values out of each row, so columns used to depend on which values row 1 happened to have.
|
|
113
|
+
- **Lookups:** a lookup that's empty in the first row no longer shows "—" in every row. Raw mode, "both" mode and Copy raw value work too, because the column is keyed `_name_value` from metadata.
|
|
114
|
+
- **Empty attributes:** every attribute in the query is a column, even when no loaded row has a value.
|
|
115
|
+
- **All attributes:** `<all-attributes />`, and a root entity without attribute elements, show every attribute that has a value in any loaded row, including link-entity all-attributes. Later pages add new columns at the end, and existing columns never move.
|
|
116
|
+
- **Cross-apply:** `matchfirstrowusingcrossapply` columns, which Dataverse returns by schema name, now appear, and can be sorted, filtered and removed.
|
|
117
|
+
- **Exists and in:** attributes inside `exists`/`in` link-entities are never grid columns. Those link types return no columns.
|
|
118
|
+
- **Excel export:** local Excel export has exactly the grid's columns, in the grid's order. It no longer adds the primary key on its own.
|
|
119
|
+
|
|
120
|
+
### 🏗️ Technical
|
|
121
|
+
|
|
122
|
+
- New `model/resultColumns.ts` (`resultColumns`, `orderByLayout`), with tests. The grid and the local Excel export both use it.
|
|
123
|
+
- `QueryResult` carries the query that ran instead of a precomputed `columns` list.
|
|
124
|
+
- `resolveColumn` maps cross-apply schema-name keys back to their attribute.
|
|
125
|
+
- #92 (header sort writing invalid orders) was already fixed by #114. New tests cover aggregate link-entity columns, an aggregate attribute without an alias, and sorting a root lookup from its `_name_value` column.
|
|
126
|
+
|
|
127
|
+
## [1.4.2-beta.4] - 2026-10-17
|
|
128
|
+
|
|
129
|
+
### 🔧 Fixed
|
|
130
|
+
|
|
131
|
+
- **Operators now follow the docs for every attribute type (#95).**
|
|
132
|
+
- Multi-line text (Memo) attributes offer Equals, Not Equals, In and Not In. The operators reference covers them under "String", and they passed the live operator matrix.
|
|
133
|
+
- Choices (multi-select) attributes offer Equals, Not Equals, In and Not In, as listed in the choices doc, alongside Contains Values / Does Not Contain Values. Equals and Not Equals use the option picker.
|
|
134
|
+
- The Operator info popover explains the operator you picked, with a link to its page:
|
|
135
|
+
- the hierarchy operators: primary key of a hierarchical entity only, up to 100 levels;
|
|
136
|
+
- the hierarchy security operators: Hierarchy Depth;
|
|
137
|
+
- Contains Values: full-text indexing, so new records can take a few seconds to match;
|
|
138
|
+
- Equals User Language.
|
|
139
|
+
|
|
140
|
+
### 🏗️ Technical
|
|
141
|
+
|
|
142
|
+
- `operators.matrix.test.ts` asserts the operator list for 28 attribute cases. Each row cites the operators reference, the choices doc or the hierarchy doc, or the #87 live matrix.
|
|
143
|
+
- Operators are only ever added on that evidence, never removed.
|
|
144
|
+
- Kept on purpose: user and team operators on user lookups and Uniqueidentifier, because they're verified live.
|
|
145
|
+
- Not offered: Equals Business on Owner (accepted, but it can never match).
|
|
146
|
+
|
|
147
|
+
## [1.4.2-beta.3] - 2026-10-17
|
|
148
|
+
|
|
149
|
+
### ✨ Added
|
|
150
|
+
|
|
151
|
+
- **Column filters that write FetchXML (#23).**
|
|
152
|
+
- Clicking a column header opens a menu: Sort (A to Z / Z to A; smallest to largest for numbers; oldest to newest for dates), **Filter by…**, and **Clear filter** when the column is filtered. Shift+click still adds the column to a multi-column sort.
|
|
153
|
+
- **Filter by…** opens the property panel's condition editor for that column: operators and values from the attribute's metadata.
|
|
154
|
+
- **Apply** adds a `<condition>` to the query and runs it again, so the filter covers every record.
|
|
155
|
+
- Filtered columns show a filter icon. Active filters show as tags above the grid ("Annual Revenue greater than 200000"; choice values by label). Dismiss a tag, or use **Clear all**, to remove filters and run the query again.
|
|
156
|
+
- **Quick search over the loaded rows (#23).**
|
|
157
|
+
- The search box above the grid filters the loaded rows as you type, matching any column's value as shown, with matches highlighted.
|
|
158
|
+
- The info bar shows how many loaded rows match.
|
|
159
|
+
- The Copy menu's "All loaded rows" becomes "All matching rows".
|
|
160
|
+
- Rows the search hides are deselected, and scrolling doesn't load more pages while searching.
|
|
161
|
+
|
|
162
|
+
### 🔧 Changed
|
|
163
|
+
|
|
164
|
+
- **Clicking a column header opens the header menu instead of sorting straight away.** Sort is the menu's first item. Shift+click still sorts directly.
|
|
165
|
+
|
|
166
|
+
### 🏗️ Technical
|
|
167
|
+
|
|
168
|
+
- New `model/columnFilters.ts`, with tests. Column filters live in their own `<filter type="and">`:
|
|
169
|
+
- FetchXML allows one `<filter>` per entity, so the group nests inside the root filter. With no root filter, the group becomes the root filter.
|
|
170
|
+
- An `or` root filter is wrapped in an `and` filter, and unwrapped again when the last column filter is removed. The store keeps the wrapper's ID (`columnFilterWrapperId`), so an `and` filter the user wrote is never unwrapped.
|
|
171
|
+
- Link-entity columns use `entityname` (the alias, or the link-entity's name), per "Filters on link-entity" on Microsoft Learn.
|
|
172
|
+
- The builder store tracks the group in `columnFilterId`, with actions `ADD_COLUMN_FILTER`, `REMOVE_COLUMN_FILTER` and `CLEAR_COLUMN_FILTERS`.
|
|
173
|
+
- A FetchXML load (editor sync) relocates the group by position when its conditions match.
|
|
174
|
+
- Loading a view, changing the entity or starting a new query drops the group.
|
|
175
|
+
- AppShell runs the query once the filter change has committed.
|
|
176
|
+
- `ConditionEditor` takes a `columnFilter` option: the column fixes the attribute, so the Entity, Attribute and Compare-to fields are hidden.
|
|
177
|
+
- Search highlighting reads the search text from a context (`SearchHighlightContext`), so typing doesn't rebuild the grid's columns. The text cell renderers wrap their text in `HighlightText`.
|
|
178
|
+
|
|
179
|
+
## [1.4.2-beta.2] - 2026-10-17
|
|
180
|
+
|
|
181
|
+
### ✨ Added
|
|
182
|
+
|
|
183
|
+
- **Copy results to the clipboard (#50).**
|
|
184
|
+
- **Right-click a cell** for: Copy value; Copy raw value (GUID, number, ISO date, option value or true/false, when it differs from what's shown); Copy row; Copy selected rows; Copy column. Copy column gives one value per line, e.g. to paste into an `in` condition.
|
|
185
|
+
- **Ctrl+C** copies the selected rows, or the focused cell when no rows are selected. Text you've highlighted copies as usual.
|
|
186
|
+
- The command bar's new **Copy** menu copies the selected rows or all loaded rows, with values **as shown** or **raw** (GUIDs, numbers, ISO dates).
|
|
187
|
+
- Rows copy as tab-separated values with the grid's headers, in the grid's column order, so they paste into Excel as cells. Values with tabs, line breaks or quotes are quoted the way Excel writes them.
|
|
188
|
+
- A toast confirms what was copied, or says the clipboard couldn't be written.
|
|
189
|
+
|
|
190
|
+
### 🏗️ Technical
|
|
191
|
+
|
|
192
|
+
- New `copyResults.ts` (TSV building, value text, clipboard write with a fallback to `toolboxAPI.utils.copyToClipboard`), with tests.
|
|
193
|
+
- `ResultsGrid` is now a `forwardRef` exposing `copyRows(scope, mode)`; `PreviewTabs` connects it to the command bar's Copy menu.
|
|
194
|
+
- The cell menu is a single controlled Fluent `Menu`, positioned at the mouse. When opened with the keyboard's context menu key, it's positioned at the cell.
|
|
195
|
+
|
|
196
|
+
## [1.4.2-beta.1] - 2026-10-17
|
|
197
|
+
|
|
198
|
+
### 🔧 Fixed
|
|
199
|
+
|
|
200
|
+
- **Selecting a node in the tree jumped to the bottom (#25).** After every selection, the tree scrolled to its *last* item, assuming the selected node was always the newest one. It now scrolls to the selected node, only within the tree, and only when the node is off-screen. Selecting a visible node no longer moves the tree.
|
|
201
|
+
- **A node added from the Add menu wasn't selected (#24).**
|
|
202
|
+
- The builder selected the new attribute, order, filter or link-entity, but the menu click also reached the tree row behind it, which selected the row again.
|
|
203
|
+
- The new node is now selected, its properties open, and the tree scrolls to it.
|
|
204
|
+
|
|
205
|
+
### 🏗️ Technical
|
|
206
|
+
|
|
207
|
+
- `TreeView.tsx`:
|
|
208
|
+
- the scroll targets `[data-fui-tree-item-value=<node id>]` inside the tree container, with `block: "nearest"`, and its timer is cleared on the next selection;
|
|
209
|
+
- the Add menus' `MenuPopover`s stop click propagation, because React events bubble through portals to the row's `onClick`.
|
|
210
|
+
|
|
211
|
+
## [1.4.1] - 2026-10-17
|
|
212
|
+
|
|
213
|
+
The v1.4.1 milestone: correctness against Dataverse, a complete properties panel, and the open bugs. It was iterated through `1.4.1-beta.1` to `1.4.1-beta.20` (detailed below). Every behaviour was checked against Microsoft Learn and a live Dataverse environment: valid FetchXML is never refused, only queries Dataverse rejects are blocked, and warnings are reserved for what can't be determined.
|
|
214
|
+
|
|
215
|
+
### ✨ Added
|
|
216
|
+
|
|
217
|
+
- **A complete, metadata-driven properties panel (#90b).** Every node type has an editor covering the FetchXML reference, with info popovers linking to the docs. Tree badges show link types, aggregates and other options at a glance.
|
|
218
|
+
- **Lossless Parse to Tree (#90a).** Every documented FetchXML attribute round-trips, as do unknown, namespaced and legacy attributes. All 60 Microsoft Learn FetchXML examples round-trip without loss. When Parse to Tree would remove or correct something, it asks first and lists the changes (#89).
|
|
219
|
+
- **Validation that matches Dataverse (#48, #89, #90b, #94, #87).**
|
|
220
|
+
- Errors are only for queries Dataverse rejects; notes never block.
|
|
221
|
+
- Every rule was verified live.
|
|
222
|
+
- Valid but unusual FetchXML (e.g. SQL 4 CDS's `xmlns:generator`, or `link-type="natural"`) is accepted.
|
|
223
|
+
- **Condition operators per attribute (#87).**
|
|
224
|
+
- The operators offered follow a live operator × attribute-type matrix.
|
|
225
|
+
- Value inputs match each operator: whole numbers for "x" operators, a fiscal period plus year, `between`, lists.
|
|
226
|
+
- The four documented fiscal "x" operators were added.
|
|
227
|
+
- **Many-to-many joins (#85).** An N:N relationship joins through its intersect entity, as documented. Intersect entities (`systemuserroles`, `teammembership`, …) can be joined to each side of their N:N relationship (#57).
|
|
228
|
+
- **Paging and Retrieve All (#58, #60).**
|
|
229
|
+
- The documented paging cookie is used, with page-number paging when Dataverse returns none.
|
|
230
|
+
- Views follow `@odata.nextLink` past 5,000 rows.
|
|
231
|
+
- **Stop** keeps the rows already loaded, and a failed page shows a message with **Retry**.
|
|
232
|
+
- **Advanced Find Only applies everywhere (#57).** It applies to entities, attributes and relationships in every entity scope. With it off, FetchXML Studio builds queries Advanced Find can't: system and customization entities, intersect entities, and hidden attributes and relationships. Unreadable attributes are never offered.
|
|
233
|
+
- **Select Attributes (#52):** a wider dialog with resizable columns, one-line cells, a type filter, "Selected only" and a selection count.
|
|
234
|
+
|
|
235
|
+
### 🔧 Fixed
|
|
236
|
+
|
|
237
|
+
- **Root entity and record IDs (#54, #59).** The root entity is detected correctly, and FetchXML is never modified: no primary key is injected.
|
|
238
|
+
- **FetchXML runs against the entity's real Web API entity set name (#61).**
|
|
239
|
+
- **Editor mode (#102).** Execute, Save and Send to Tool run exactly what's in the editor.
|
|
240
|
+
- **Escaping and aliases (#63, #94).**
|
|
241
|
+
- Every generated value is escaped, and apostrophes stay literal for Power Automate.
|
|
242
|
+
- Corrected aliases update their references.
|
|
243
|
+
- The Intellisense, Power Automate and Web API snippets follow the docs.
|
|
244
|
+
- **Results grid columns (#114).**
|
|
245
|
+
- Edit / Add columns and header sorting write valid FetchXML for aliased and link-entity columns and aggregate queries.
|
|
246
|
+
- Columns of link-entities without an alias show in the grid.
|
|
247
|
+
- **Parse to Tree, files and views keep their query (#57).** Changing the publisher or solution selection never clears the query.
|
|
248
|
+
- **Selecting a new link-entity no longer crashes the app.**
|
|
249
|
+
- **Dark theme (#67)** covers the whole tool and follows PPTB's theme live, including System.
|
|
250
|
+
- **Link-entities (#86, #88):** switching to not any / not all keeps filters, and "None" is never written as a value.
|
|
251
|
+
- **The "+ New" button** is no longer covered by the entity box in a narrow pane (#53).
|
|
252
|
+
- **The tool icon** is bundled under `dist/` and fills PPTB's icon slot (#62, #121). It shows in PPTB once PowerPlatformToolBox/desktop-app#738 ships.
|
|
253
|
+
|
|
254
|
+
### 🏗️ Technical
|
|
255
|
+
|
|
256
|
+
- **Dependencies:** Fluent UI React v9 upgraded to 9.74.9 (#104), and `package-lock.json` is committed.
|
|
257
|
+
- **Tests and lint:** Vitest with 507 tests (#64). Lint passes with zero warnings and runs as `eslint . --max-warnings=0` (#65).
|
|
258
|
+
|
|
259
|
+
## [1.4.1-beta.20] - 2026-10-17
|
|
260
|
+
|
|
261
|
+
### 🔧 Fixed
|
|
262
|
+
|
|
263
|
+
- **The tool icon looked small in PPTB (#121).** PPTB draws tool icons in a 20×20 box, but the hexagon only filled 56×48 of the icons' 64×64 canvas, so it showed at about 17×15 px.
|
|
264
|
+
- Both icons (`icon.svg` and the Insider `icon-insider.svg`) now crop their `viewBox` to the hexagon: `4 8 56 48`. They draw at about 20×17 px.
|
|
265
|
+
- The artwork is unchanged; no shape or stroke falls outside the new box.
|
|
266
|
+
- The build still emits the right variant to `dist/icons/icon.svg`, and `pptb-validate` passes.
|
|
267
|
+
|
|
268
|
+
## [1.4.1-beta.19] - 2026-10-17
|
|
269
|
+
|
|
270
|
+
### 🔧 Fixed
|
|
271
|
+
|
|
272
|
+
Lint is clean: `npm run lint` now runs `eslint . --max-warnings=0` and passes with zero warnings (#65). The hook-dependency warnings were fixed rather than suppressed, and two of them were real bugs:
|
|
273
|
+
- **The Code tab's Web API snippet could show a stale entity set name.** The snippet didn't update when the entity set name finished loading.
|
|
274
|
+
- **Loading FetchXML from a file could report through a stale editor callback.** That handler didn't list the callback as a dependency.
|
|
275
|
+
|
|
276
|
+
### 🏗️ Technical
|
|
277
|
+
|
|
278
|
+
- `builderStore.tsx`: the builder's actions are created once (`dispatch` never changes), and the context value is memoized on the state. Consumers can list builder actions as hook dependencies, and they only re-render when the builder's state changes.
|
|
279
|
+
- `AppShell.tsx`:
|
|
280
|
+
- the layout sync effect lists `syncLayoutWithFetchXml`, which is now stable;
|
|
281
|
+
- the 1:N / N:N check uses `LinkEntityNode[]` instead of `typeof builder…`;
|
|
282
|
+
- record deletes re-run the query through a ref to the latest `handleExecute`, called after the `await`, instead of a version captured when the callback was created.
|
|
283
|
+
- `CodePanel.tsx` / `FetchXmlEditor.tsx`: the missing dependencies above.
|
|
284
|
+
- `TreeView.tsx`: the one `eslint-disable` without a reason now explains why the effect runs only on selection changes.
|
|
285
|
+
- `eslint.config.js`: ignores `.claude/`, which holds local, git-ignored prototypes.
|
|
286
|
+
## [1.4.1-beta.18] - 2026-10-17
|
|
287
|
+
|
|
288
|
+
### 🔧 Fixed
|
|
289
|
+
|
|
290
|
+
- **Select Attributes: long names wrapped onto several lines and the columns couldn't be resized (#52).** Design option A, reviewed in a prototype:
|
|
291
|
+
- The dialog is wider: up to 1000px, never wider than the window.
|
|
292
|
+
- The Display name, Logical name and Type columns are resizable and sortable. Each cell stays on one line, long text ends with an ellipsis, and the full text shows in a tooltip.
|
|
293
|
+
- Search matches display or logical name. A **type filter**, a **Selected only** switch and an "N of M selected" count help with long attribute lists.
|
|
294
|
+
- Types use `AttributeTypeName`, so images, files and multi-select choices no longer all show as "Virtual".
|
|
295
|
+
- **"+ New" was overlapped by the entity box in a narrow left pane (#53).** Fluent's Combobox has a built-in minimum width (about 250px).
|
|
296
|
+
- The entity, publisher, solution and view boxes now shrink with the pane.
|
|
297
|
+
- Below 300px "New" keeps only its icon, with a "New query" tooltip and label.
|
|
298
|
+
|
|
299
|
+
### 🏗️ Technical
|
|
300
|
+
|
|
301
|
+
- `SelectAttributesDialog.tsx`: `resizableColumns` + `columnSizingOptions`, truncated cells with tooltips, type / selected-only filters, count in a fluid `DialogActions`.
|
|
302
|
+
- `EntitySelector.tsx` / `LoadViewPicker.tsx`: comboboxes and their inputs use `minWidth: 0`; the "New" button switches to icon-only with a container query (the toolbar is already an inline-size container).
|
|
303
|
+
|
|
304
|
+
## [1.4.1-beta.17] - 2026-10-16
|
|
305
|
+
|
|
306
|
+
### 🔧 Fixed
|
|
307
|
+
|
|
308
|
+
- **Changing the publisher or solution selection cleared the query** when the new selection didn't include its entity (#57 follow-up). Selecting the Default Solution, for example, wiped a `systemuserroles` query with no warning. Deselecting asked first, but still reset the tree.
|
|
309
|
+
- The scope only filters the entity picker, so the query is now never cleared.
|
|
310
|
+
- An entity outside the selection shows by its logical name, and Load View still works for it (from the full entity list).
|
|
311
|
+
- The "Confirm Publisher / Solution Change" dialog, which warned that the tree would be reset, is removed.
|
|
312
|
+
|
|
313
|
+
### 🏗️ Technical
|
|
314
|
+
|
|
315
|
+
- `EntitySelector.tsx`: no auto-clear effect and no confirm dialog; `selectedEntityMetadata` falls back to the full entity list.
|
|
316
|
+
|
|
317
|
+
### 🧪 Test (PPTB, sandbox)
|
|
318
|
+
|
|
319
|
+
- Parse to Tree on `<fetch><entity name="systemuserroles"><attribute name="roleid" /><link-entity name="role" from="roleid" to="roleid" alias="r"><attribute name="name" /></link-entity></entity></fetch>`. Then select the Default Publisher and Default Solution, and deselect both: the query stays every time, with no dialog; the entity box shows `systemuserroles`; Load View is available.
|
|
320
|
+
|
|
321
|
+
## [1.4.1-beta.16] - 2026-10-16
|
|
322
|
+
|
|
323
|
+
### 🔧 Fixed
|
|
324
|
+
|
|
325
|
+
**Advanced Find Only** now does what it says, everywhere (#57). [`IsValidForAdvancedFind`](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.metadata.entitymetadata.isvalidforadvancedfind) means "shown in Advanced Find"; it isn't a query restriction. Turning it off is how FetchXML Studio builds queries Advanced Find can't. Checked against the sandbox:
|
|
326
|
+
- **Hidden entities were missing in the Publisher → Solution and Solution scopes** (`userquery`, the reported case). Those scopes always loaded Advanced Find entities only. The setting now applies in every scope.
|
|
327
|
+
- Verified: 347 of 421 hidden entities query fine with FetchXML, including `userquery`, `savedquery`, `systemform`, `sdkmessageprocessingstep` and the intersect entities.
|
|
328
|
+
- The rest return a Dataverse error, shown on Execute as usual.
|
|
329
|
+
- **Hidden attributes and relationships were never offered**, even with the setting off. With it off, every picker now offers them: the tree, Select attributes, conditions, orders, link-entities, Add columns and relationships.
|
|
330
|
+
- Verified: 1,028 of 1,053 hidden attributes and all 1,593 hidden relationships work.
|
|
331
|
+
- **Attributes that can't be read are never offered.** `IsValidForRead = false` attributes fail in every query (`0x80041a08`, 25 of 25).
|
|
332
|
+
- **Operators for attribute types that only appear with the setting off** (verified on each):
|
|
333
|
+
- label attributes such as `statuscodename` (Virtual): `null`, `not-null`, `eq`, `like`; they can be sorted, but not grouped or aggregated (`0x8004e01d` / `0x80044150`);
|
|
334
|
+
- images, files, managed properties and entity names: `null` and `not-null` only (`eq` / `like` fail: `0x80040203`);
|
|
335
|
+
- PartyList (`to`, `cc`, `from`): no conditions; every condition fails (`0x80040203`).
|
|
336
|
+
- **Attributes already in a query keep their metadata** (operators, value inputs, grid formatting) whatever the setting. A pasted query that uses a hidden attribute works either way.
|
|
337
|
+
- The setting's description and tooltip say what it covers.
|
|
338
|
+
- **Parse to Tree emptied the tree and the editor** in the Publisher → Solution and Solution scopes when the selected solutions didn't include the query's entity (reported with `systemuserroles`). The same happened to opened files, loaded views and queries from other tools, and toggling Advanced Find Only would have done it too.
|
|
339
|
+
- The scope only filters the entity picker. The query is now cleared only after *you* change the publisher or solution selection, once the new entity list has loaded without the query's entity.
|
|
340
|
+
- Otherwise the entity box shows the query's logical name.
|
|
341
|
+
- **Intersect entities can be joined from the relationship picker.** `systemuserroles`, `teammembership`, `listmember` and the rest have no many-to-one relationships for their attributes (plain Uniqueidentifiers), so nothing was offered. They now offer a join to each side of the many-to-many relationship they implement, e.g. `systemuserroles → role via roleid (systemuserroles_association)`, in the relationship picker and in Add columns → Related.
|
|
342
|
+
- Verified on every intersect entity in the sandbox: 141 entities, 302 joins. Every join runs except on the few intersect entities that can't be queried at all.
|
|
343
|
+
- Picking one adds `<link-entity name="role" from="roleid" to="roleid">`.
|
|
344
|
+
- **Selecting a new link-entity crashed the app** (blank screen). Its placeholder name has no attributes, and the attribute picker's fallback dropped a prop Fluent needs (`selectedOptions`).
|
|
345
|
+
- **A new link-entity kept its placeholder alias** (`new_entity`) after a 1:N or N:1 relationship was picked. It now gets an alias for the related entity (e.g. `systemuser`).
|
|
346
|
+
|
|
347
|
+
### 🏗️ Technical
|
|
348
|
+
|
|
349
|
+
- New `state/metadataScope.ts`: scope rules (`isEntityInScope`, `isAttributeInScope`, `isRelationshipInScope`) and the current setting (`metadataScope`, `useAdvancedFindOnly`).
|
|
350
|
+
- `dataverseMetadata.ts`:
|
|
351
|
+
- attributes and relationships are fetched once, unfiltered, and filtered locally;
|
|
352
|
+
- `loadAllEntityAttributes` for lookups, `loadEntityAttributes` for pickers;
|
|
353
|
+
- `loadAllEntities` shares the Publisher / Solution entity cache.
|
|
354
|
+
- `pptbClient.ts`: `getAllEntitiesForScopes` (replaces the Advanced Find–only `getAllAdvancedFindEntities`); the unused `getAdvancedFindEntitiesByNames` is removed; attributes select `IsValidForRead`.
|
|
355
|
+
- `operators.ts`: Virtual attributes are typed by `AttributeTypeName` (multi-select, image, file, label).
|
|
356
|
+
- `attributeCapabilities.ts`: VirtualType can be sorted.
|
|
357
|
+
- `manyToMany.ts`: `planIntersectEntityJoins` (an intersect entity's joins to each side) and `relationshipKey` (the two joins share a schema name); `loadEntityRelationships` adds them for intersect entities.
|
|
358
|
+
- `EntitySelector.tsx`: the query is cleared only after a publisher / solution change made by the user.
|
|
359
|
+
- `aliasUtils.ts`: `collectLinkEntityAliases` and `generateUniqueAlias` moved here from the builder store.
|
|
360
|
+
|
|
361
|
+
### 🧪 Test (sandbox)
|
|
362
|
+
|
|
363
|
+
- With Advanced Find Only off, every scope: `<fetch top="5"><entity name="userquery"><attribute name="name" /><attribute name="returnedtypecode" /></entity></fetch>`.
|
|
364
|
+
- Intersect entity: `<fetch><entity name="systemuserroles"><attribute name="roleid" /><link-entity name="role" from="roleid" to="roleid" alias="r"><attribute name="name" /></link-entity></entity></fetch>`.
|
|
365
|
+
- Hidden attribute: `<fetch top="5"><entity name="account"><attribute name="name" /><attribute name="versionnumber" /><order attribute="versionnumber" descending="true" /></entity></fetch>`.
|
|
366
|
+
- Label attribute: `<fetch top="5"><entity name="contact"><attribute name="fullname" /><filter><condition attribute="statuscodename" operator="like" value="%Active%" /></filter><order attribute="statuscodename" /></entity></fetch>`.
|
|
367
|
+
- In PPTB, Publisher → Solution scope with a solution that doesn't include `systemuserroles`: Parse to Tree on the intersect query keeps the tree; Execute returns 407 rows. Added the `systemuser` join from the relationship picker and Full Name from Add columns → Related: `<link-entity name="systemuser" from="systemuserid" to="systemuserid" alias="systemuser"><attribute name="fullname" /></link-entity>`, 407 rows.
|
|
368
|
+
|
|
369
|
+
## [1.4.1-beta.15] - 2026-10-16
|
|
370
|
+
|
|
371
|
+
### 🔧 Fixed
|
|
372
|
+
|
|
373
|
+
Paging, **Retrieve All** and infinite scroll (#58, #60). Every case was run against the sandbox through the production code, following [Page results using FetchXml](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/page-results).
|
|
374
|
+
- **The paging cookie was never used (#58).** FetchXML Studio read `@Microsoft.Dynamics.CRM.pagingcookie`, which the Web API doesn't send.
|
|
375
|
+
- The cookie is now read from `@Microsoft.Dynamics.CRM.fetchxmlpagingcookie`: the `pagingcookie` attribute of that element, URL-decoded twice, as documented.
|
|
376
|
+
- Every page after the first was fetched by page number only, which Microsoft doesn't recommend. Verified: pages fetched with the cookie match page-number paging, with no duplicates.
|
|
377
|
+
- **Queries Dataverse returns no cookie for** (sorted by a link-entity attribute, or `distinct`) page by page number. That's the documented fallback, and a note above the grid says so.
|
|
378
|
+
- Dataverse reports page 2 as the next page on *every* page of these queries (verified). The next page is now always the page just fetched + 1, and an empty page ends paging, so a loop can't repeat a page.
|
|
379
|
+
- **Retrieve All could mix results (#60).**
|
|
380
|
+
- Changing the entity, loading a view or starting a new query while pages were loading brought the old rows back. Pages from an older run are now dropped.
|
|
381
|
+
- A record delete re-runs the query directly. That could start a second loop alongside the first; the first is now dropped.
|
|
382
|
+
- A failed page was only logged to the console. Now a message above the grid says how many rows loaded and which page failed, with **Retry**.
|
|
383
|
+
- **Stop button (#60).** While Retrieve All runs, **Stop** appears at the start of the results command bar. The loaded rows stay; scrolling to the end loads more one page at a time.
|
|
384
|
+
- **System and personal views stopped at 5,000 rows.** Views return `@odata.nextLink` instead of a cookie (verified); FetchXML Studio now follows it, for Retrieve All and scrolling alike.
|
|
385
|
+
- **The query's own paging was ignored:**
|
|
386
|
+
- `count` was read from the tree instead of the FetchXML that ran (editor mode). Each page now keeps the executed query's `count`.
|
|
387
|
+
- `page="3"` in the query continued at page 2. It now continues at page 4.
|
|
388
|
+
- `top` is read from the executed FetchXML; queries with `top` aren't paged, as documented.
|
|
389
|
+
- `page`, `paging-cookie` and `count` in single quotes were added again instead of replaced, which made the XML invalid.
|
|
390
|
+
- Cookie values containing `$&`, `$1` and similar were mangled when written into the query.
|
|
391
|
+
- **Each page's full response was logged to the console**, which kept every row of a large Retrieve All in memory. It's now debug-only.
|
|
392
|
+
|
|
393
|
+
### 🏗️ Technical
|
|
394
|
+
|
|
395
|
+
- New `api/paging.ts`: `parseFetchXmlPagingCookie`, `readFetchPagingOptions`, `nextLinkToQueryPath`, `advancePaging`, `hasNextPage`, and the `PagingState` / `PagingNotice` types.
|
|
396
|
+
- `pptbClient.ts`: one `pagingFromResponse` for FetchXML and view responses; new `executeNextLink`.
|
|
397
|
+
- `fetchxml.ts`: `addPagingToFetchXml` replaces either quote style and keeps the query's `count` unless one is given.
|
|
398
|
+
- `AppShell.tsx`: one paging state (in a ref, so the page loops don't read stale state), plus result and page run tokens.
|
|
399
|
+
|
|
400
|
+
### 🧪 Test FetchXML (sandbox)
|
|
401
|
+
|
|
402
|
+
- Cookie paging: `<fetch count="50"><entity name="systemuser"><attribute name="systemuserid" /><order attribute="fullname" /><order attribute="systemuserid" /></entity></fetch>`: 234 of 234 users, no duplicates.
|
|
403
|
+
- No cookie (distinct): `<fetch count="50" distinct="true"><entity name="systemuser"><attribute name="fullname" /><order attribute="fullname" /></entity></fetch>`: every distinct name once.
|
|
404
|
+
- No cookie (link-entity order): `<fetch count="40"><entity name="systemuser"><attribute name="systemuserid" /><order entityname="bu" attribute="name" /><order attribute="systemuserid" /><link-entity name="businessunit" from="businessunitid" to="businessunitid" alias="bu" link-type="inner"><attribute name="name" /></link-entity></entity></fetch>`: 234 of 234.
|
|
405
|
+
- Starting page: `<fetch count="50" page="3"><entity name="systemuser"><attribute name="systemuserid" /><order attribute="systemuserid" /></entity></fetch>`: the users after the first 100.
|
|
406
|
+
- Default page size: `<fetch><entity name="account"><attribute name="accountid" /></entity></fetch>`: 3 pages, 15,000 unique accounts.
|
|
407
|
+
- View: **Active Accounts** (system view): 3 pages through `@odata.nextLink`, 15,000 unique.
|
|
408
|
+
- `<fetch top="10"><entity name="account"><attribute name="accountid" /></entity></fetch>`: 10 rows, no paging.
|
|
409
|
+
|
|
410
|
+
## [1.4.1-beta.14] - 2026-10-15
|
|
411
|
+
|
|
412
|
+
### 🔧 Fixed
|
|
413
|
+
|
|
414
|
+
Condition operators and their values (#87). Every operator in the [operators reference](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/reference/operators) was run against every Advanced Find attribute type on the sandbox (1,640 queries), plus the value shapes each operator takes.
|
|
415
|
+
- **"x" and fiscal operators showed a date picker.** `last-x-days`, `olderthan-x-months`, `in-fiscal-period` and the rest wrote an ISO date, which Dataverse rejects (`0x80041139`).
|
|
416
|
+
- They now take a whole number above 0. Verified: 0, negatives, decimals and dates fail.
|
|
417
|
+
- `in-fiscal-year` takes a four-digit year (verified: `23` fails).
|
|
418
|
+
- The three period-and-year operators take a **Fiscal period** and a **Fiscal year**, written as two `<value>` elements, as documented.
|
|
419
|
+
- **Four documented operators were missing:** `last-x-fiscal-years`, `last-x-fiscal-periods`, `next-x-fiscal-years` and `next-x-fiscal-periods` are now selectable.
|
|
420
|
+
- **Changing the operator kept a value of the wrong shape** (a date for a number, a list for a single value). The value is now kept only when the new operator takes the same shape.
|
|
421
|
+
- **Operators offered per attribute follow what Dataverse accepts:**
|
|
422
|
+
- `contain-values` / `not-contain-values` only for multi-select choices (single choices fail: `0x80048415`); multi-select choices get their own list instead of every operator, and their option values are offered.
|
|
423
|
+
- The user / team operators (`eq-userid`, `eq-userteams`, …) aren't offered for Customer lookups (`0x8004034b`).
|
|
424
|
+
- The hierarchy operators (`under`, `above`, …) only for the primary key of a hierarchical entity (lookups fail: `0x80040203`).
|
|
425
|
+
- Hour and minute operators are left out for date-only *behaviour* (`0x80048415`), but no longer for a date-only *format* with user-local behaviour, which accepts them (verified).
|
|
426
|
+
- Strings get `gt` / `ge` / `lt` / `le`, as documented.
|
|
427
|
+
- **New checks (Validate / Execute), verified live:**
|
|
428
|
+
- an "x" operator without a whole number above 0;
|
|
429
|
+
- a fiscal year that isn't four digits;
|
|
430
|
+
- `between` or a period-and-year operator without exactly two values;
|
|
431
|
+
- `in` / `not-in` / `contain-values` without `<value>` elements;
|
|
432
|
+
- a single-value operator with `<value>` elements or no value.
|
|
433
|
+
- A value on an operator that takes none is a note: Dataverse ignores it.
|
|
434
|
+
- None of the 60 Microsoft Learn examples trigger them.
|
|
435
|
+
|
|
436
|
+
### 🏗️ Technical
|
|
437
|
+
|
|
438
|
+
- `operators.ts`: `getOperatorsForAttribute` (type, multi-select, primary key, date-only behaviour, hierarchical entity), `operatorValueShape` and `adaptValueForOperator`; new value types `integer`, `year`, `fiscalPeriodYear`.
|
|
439
|
+
- `fetchxmlRules.ts`: `conditionValueIssue`.
|
|
440
|
+
- The option set request also casts to `MultiSelectPicklistAttributeMetadata`.
|
|
441
|
+
|
|
442
|
+
## [1.4.1-beta.13] - 2026-10-15
|
|
443
|
+
|
|
444
|
+
### 🔧 Fixed
|
|
445
|
+
|
|
446
|
+
Results grid **Edit columns / Add columns** and **header sorting** (#114). Each change was checked against the sandbox.
|
|
447
|
+
- **Removing a column only worked for un-aliased root attributes.** Aliased attributes and link-entity columns were silently kept.
|
|
448
|
+
- Removing now finds the attribute behind any column.
|
|
449
|
+
- It also removes sorts that use the attribute's alias (Dataverse rejects an order alias that no longer exists).
|
|
450
|
+
- An **outer** link-entity left with nothing in it is removed too. A 1:N join would otherwise keep multiplying the rows without showing anything. Inner joins are kept, because they still filter.
|
|
451
|
+
- **All columns could be removed.** A query with no attributes returns every attribute, so Apply is now disabled until at least one column remains.
|
|
452
|
+
- **Add columns in aggregate queries** added plain attributes, which Dataverse rejects (every attribute needs Group by or an aggregate, and an alias). It's now unavailable there, with the reason.
|
|
453
|
+
- **The Related tab offered attributes the link-entity already returns.** It now leaves them out.
|
|
454
|
+
- **Columns of link-entities without an alias never showed in the grid.**
|
|
455
|
+
- Dataverse names them `{name}{N}.attribute`, where N is the position among the joined link-entities (verified, and documented on the link-entity reference), not `name.attribute`.
|
|
456
|
+
- The layout and the grid now use the same names. This affects queries loaded from views or pasted FetchXML.
|
|
457
|
+
- **Header sorting wrote orders Dataverse rejects:**
|
|
458
|
+
- aggregate queries got `<order attribute>` instead of `<order alias>`;
|
|
459
|
+
- an aliased link-entity attribute got `<order attribute="alias">` on the root (`0x80041103`); it's now `entityname` + attribute name;
|
|
460
|
+
- multi-select option sets could be sorted (`0x80050224`); unsortable types now show a message instead.
|
|
461
|
+
- **Single-column sort:** it replaces every order in the query, including orders inside link-entities, so the results are sorted by that column only.
|
|
462
|
+
|
|
463
|
+
### 🏗️ Technical
|
|
464
|
+
|
|
465
|
+
- `model/queryColumns.ts`: one model of the result columns (keys exactly as Dataverse returns them) shared by the layout, the grid, column removal and sorting. Tests in `queryColumns.test.ts`.
|
|
466
|
+
- `setSort` takes a planned order (root, or inside a link-entity when the root can't target it).
|
|
467
|
+
|
|
468
|
+
## [1.4.1-beta.12] - 2026-10-15
|
|
469
|
+
|
|
470
|
+
### 🔧 Fixed
|
|
471
|
+
|
|
472
|
+
- **Apostrophes stay literal in generated FetchXML (#63).** Values were written with `'`. That's valid XML, but it broke Power Automate expressions pasted into the Fetch Xml Query field (e.g. `@{body('Parse_payload')?['Student']}`).
|
|
473
|
+
- Attribute values are always in double quotes, so only `&`, `<`, `>` and `"` are escaped; `<value>` text escapes `&`, `<` and `>`.
|
|
474
|
+
- Verified live: raw apostrophes are accepted in both forms.
|
|
475
|
+
- **Every value in generated FetchXML is escaped (#63, #94).** Entity, attribute and link-entity names, aliases, `from` / `to`, order references, `hint` and `operator` were written unescaped, so pasted or parsed values with `"`, `&` or `<` produced broken XML.
|
|
476
|
+
- **Corrected aliases no longer break their references (#94).** When Parse to Tree corrects an invalid alias, it now updates every reference to it as well: condition and order `entityname`, `valueof` (including aliases that contained dots), and aggregate order aliases. The fix is listed with the corrections.
|
|
477
|
+
- Verified live: a link-entity alias with dots fails as soon as `valueof` refers to it (`0x80040216`); the corrected query runs.
|
|
478
|
+
- **New link-entity aliases can't collide with link-entities inside filters (#94).** Generated aliases now take any / not any / all / not all link-entities into account.
|
|
479
|
+
- **Intellisense follows the FetchXML reference (#94).**
|
|
480
|
+
- Value suggestions now work for hyphenated attributes (`link-type`, `paging-cookie`, `no-lock`, `utc-offset`).
|
|
481
|
+
- Added the documented attributes that were missing: `aggregatelimit`, `datasource`, `latematerialize`, `options` (with the documented hints) and `useraworderby` on fetch; `entityname` on order; `rowaggregate` on attribute; `hint` and both quick find overrides on filter.
|
|
482
|
+
- Removed `order` as a child of `<fetch>` and the condition `aggregate`, which Dataverse ignores.
|
|
483
|
+
- Undocumented attributes are suggested only where verified and say so: `utc-offset`, `version`, `mapping`, `output-format`, `min-active-row-version`, link-entity prefiltering, `natural`, and the saved-view `uiname` / `uitype` / `uihidden`.
|
|
484
|
+
- The operator list is the parser's own list, so the two can't drift apart.
|
|
485
|
+
- Condition `attribute` stays required: the reference lists it as optional, but Dataverse rejects a condition without it (`0x8004111e`).
|
|
486
|
+
- **Power Automate Top Count comes from `top` only (#94).** It used to take `count`, which is a page size, not a limit ([page-results](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/page-results)).
|
|
487
|
+
- **Web API snippet (#94).** It adds the documented `Prefer: odata.include-annotations="*"` header (formatted values, paging cookie and more-records annotations) and the paging-cookie steps.
|
|
488
|
+
- **More than one filter in an entity is noted (#94).** The reference allows one. Dataverse accepts several and combines them with *and* (verified, same rows as one nested filter), so it's a note, never an error.
|
|
489
|
+
|
|
490
|
+
### 🏗️ Technical
|
|
491
|
+
|
|
492
|
+
- `KNOWN_OPERATORS` moved to `operators.ts`, shared by the parser and the intellisense.
|
|
493
|
+
- `collectLinkEntityAliases` uses `collectAllLinkEntities`.
|
|
494
|
+
- `correctness94.test.ts`: escaping round trips, alias references, the intellisense catalogue against the reference, and the code generators.
|
|
495
|
+
|
|
496
|
+
## [1.4.1-beta.11] - 2026-10-15
|
|
497
|
+
|
|
498
|
+
### 🔧 Fixed
|
|
499
|
+
|
|
500
|
+
- **Many-to-many relationships from the relationship picker return the right rows (#85).** Choosing an N:N relationship built a single link-entity straight to the related entity, using the intersect entity's attribute names. That silently returned 0 rows (or failed, depending on the side).
|
|
501
|
+
- It now builds the [documented pattern](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/join-tables#many-to-many-relationships): the link-entity becomes the intersect entity (`intersect="true"`, joined on the parent's primary key), and the related entity is nested inside it, with the alias, so attributes are added there.
|
|
502
|
+
- The attributes come from the relationship metadata (`IntersectEntityName`, `Entity1/2IntersectAttribute`) and both entities' primary keys. It works from either side, and for self-referential relationships.
|
|
503
|
+
- **Verified live:** for every many-to-many relationship with data in the sandbox (47 checks from both sides, system, custom and self-referential), the join returned exactly the intersect entity's pairs.
|
|
504
|
+
|
|
505
|
+
- **Attribute pickers show their value even when it isn't in the attribute list.** For example, intersect entities usually have no Advanced Find attributes, so the From / To pickers on an N:N join looked empty.
|
|
506
|
+
|
|
507
|
+
### 🏗️ Technical
|
|
508
|
+
|
|
509
|
+
- `model/manyToMany.ts`: `planManyToManyJoin`, with tests.
|
|
510
|
+
|
|
511
|
+
## [1.4.1-beta.10] - 2026-10-15
|
|
512
|
+
|
|
513
|
+
### 🔧 Fixed
|
|
514
|
+
|
|
515
|
+
- **Dark theme covers the whole tool (#67).** The left pane (tree and properties) and any surface without its own background showed the page's white canvas behind dark-theme text, which made them unreadable in dark mode. The theme background now covers the whole tool. Native controls (scrollbars, date inputs) follow the theme too (`color-scheme`).
|
|
516
|
+
- **The tool follows the PPTB theme live, including System (#67).**
|
|
517
|
+
- On `settings:updated`, the tool now always asks the host for the resolved theme (`getCurrentTheme()`). It used to read the raw setting, and "system" was treated as light.
|
|
518
|
+
- The host sends no event when the OS theme changes while ToolBox is set to System. The tool now also listens for the OS colour scheme and asks the host again, so it switches without a reload. An explicit Light or Dark setting is kept.
|
|
519
|
+
- Only the latest theme request applies.
|
|
520
|
+
- Verified against the PPTB 1.2.7 host source: `getCurrentTheme()` resolves System from `nativeTheme`, and `settings:updated` carries only the changed settings.
|
|
521
|
+
|
|
522
|
+
### 🏗️ Technical
|
|
523
|
+
|
|
524
|
+
- `usePptbContext.test.ts`: the theme contract (settings:updated with System, OS changes, explicit settings, cleanup, standalone).
|
|
525
|
+
- Removed the theme debug logging from `AppShell`.
|
|
526
|
+
|
|
527
|
+
## [1.4.1-beta.9] - 2026-10-14
|
|
528
|
+
|
|
529
|
+
### ✨ Added
|
|
530
|
+
|
|
531
|
+
- **A complete, metadata-driven properties panel (#90b).** Every node type now has an editor that covers the FetchXML reference. The "coming in a later phase" placeholders are gone. Fields use Fluent UI v9 info popovers with a **Learn more** link to the matching Microsoft Learn article, sentence-case labels and Entity / Attribute / Logical Name terms.
|
|
532
|
+
- **Fetch Query:**
|
|
533
|
+
- **Query:** aggregate, distinct, total record count, sort option sets by value.
|
|
534
|
+
- **Paging and limits:** top, count, page and paging cookie. `top` can't be combined with paging, so the conflicting fields explain why.
|
|
535
|
+
- **Aggregation:** `aggregatelimit` (1–50,000).
|
|
536
|
+
- **Data source:** Active / Retained. Retained is disabled when the entity's long-term retention is off.
|
|
537
|
+
- **Performance:** late materialize, the documented query hints as checkboxes, and any other hints kept as tags.
|
|
538
|
+
- **Advanced:** UTC offset.
|
|
539
|
+
- **Turning off aggregate** lists what it would remove or change (Group by, aggregates, alias sorts) and asks first.
|
|
540
|
+
- **Entity:** the entity's logical and display name, plus report prefiltering (`enableprefiltering`, `prefilterparametername`).
|
|
541
|
+
- **All attributes:** what it does, Microsoft's guidance against it, and a Remove button.
|
|
542
|
+
- **Attribute:**
|
|
543
|
+
- **Role:** Group by or Aggregate.
|
|
544
|
+
- **Function:** only the functions the attribute's type supports.
|
|
545
|
+
- **Group by options:** date grouping (required for date attributes), time zone, and unique values for count of values.
|
|
546
|
+
- **Count child records:** offered on the primary key of hierarchical entities.
|
|
547
|
+
- **Renaming an alias** updates the sorts that use it.
|
|
548
|
+
- **Sort:**
|
|
549
|
+
- **Regular queries:** an attribute picker limited to sortable types.
|
|
550
|
+
- **Aggregate queries:** a list of the aliases in scope.
|
|
551
|
+
- **Entity:** sort a link-entity first.
|
|
552
|
+
- **Filter:** all (and) / any (or), the union hint (only for `or`, one per query), quick find, and the quick find record limit overrides.
|
|
553
|
+
- **Condition:** Entity (`entityname`, filter after an outer join), attribute, operator and value. **Compare to** chooses between a value and another attribute.
|
|
554
|
+
- **Link-entity:**
|
|
555
|
+
- entity, From / To attribute pickers, link type, alias and intersect;
|
|
556
|
+
- link types are offered by placement: inside a filter only any / not any / all / not all;
|
|
557
|
+
- descriptions follow the docs, including that `all` / `not all` mean the opposite of what the names suggest.
|
|
558
|
+
- **Tree badges:**
|
|
559
|
+
- **Fetch Query:** Aggregate, Distinct, Retained, Hints.
|
|
560
|
+
- **Attributes:** Group by (with date grouping), the aggregate function, Unique, Child count.
|
|
561
|
+
- **Filters:** Union, Quick find.
|
|
562
|
+
- **Any node with a rule error:** a red **Error** badge.
|
|
563
|
+
- **Conditions that compare attributes** read `attribute eq → other`.
|
|
564
|
+
- **Editor mode follows the properties panel.** While editor mode is on, panel edits update the editor when the editor hasn't changed the query. If it has, the editor keeps your text and says so.
|
|
565
|
+
|
|
566
|
+
### 🔧 Fixed
|
|
567
|
+
|
|
568
|
+
- **Comparing to another attribute (`valueof`) only offers what Dataverse accepts (#94).**
|
|
569
|
+
- The choice appears only for eq, ne, gt, ge, lt and le (others fail with `0x80048415`). Changing to another operator clears it.
|
|
570
|
+
- **Which entities are offered.** The docs show the documented same-row and link-entity-alias cases; the picker offers only those, plus what was verified to work from where the condition is:
|
|
571
|
+
- the root entity by its logical name;
|
|
572
|
+
- from the root entity's filters: any joined link-entity alias;
|
|
573
|
+
- from a link-entity's filter: only link-entities joined before it (its own children and later link-entities fail with `0x80040216`).
|
|
574
|
+
- A link-entity name without an alias, or a link-entity inside a filter, is never offered.
|
|
575
|
+
- **Which attributes are offered.** The docs say "the same type". FetchXML Studio offers the same type plus the pairs verified to work, with all six operators: Integer / option set / status / state / entity name; Decimal / Money; Lookup / Customer / Owner / unique identifier; String / Memo; Boolean / managed property; DateTime (any behaviour).
|
|
576
|
+
- Verified with a live matrix over every Advanced Find attribute type: about 2,000 queries, including calculated, rollup, composite and date-only attributes.
|
|
577
|
+
- Pairs across these groups fail (`0x80040203`), so they aren't offered.
|
|
578
|
+
- Multi-select option sets, and any type that wasn't verified, aren't offered at all.
|
|
579
|
+
- **Conditions on a link-entity attribute (`entityname`) load that entity's metadata (#94).** Numeric limits and date-only rules previously came from the wrong entity.
|
|
580
|
+
- **Switching a link-entity to not any / not all no longer deletes its filters and nested link-entities (#86).**
|
|
581
|
+
- Those filters are what these types evaluate.
|
|
582
|
+
- Switching to any type that returns no attributes (exists, in, any, not any, all, not all) now lists the attributes, all-attributes and sorts that Dataverse would reject, and asks before removing them.
|
|
583
|
+
- The tree disables adding attributes and sorts to those link-entities, and **Add Columns** creates a new link-entity instead of adding to one of them.
|
|
584
|
+
- **From / To on a link-entity are no longer marked required.** The docs say they're optional; the panel now explains what Dataverse does when one or both are missing.
|
|
585
|
+
- **Intersect** is described correctly: it only signals intent and doesn't change how the query runs.
|
|
586
|
+
- **"Visible in Results" couldn't be turned off.** `visible` isn't in the FetchXML reference, and Dataverse ignores it (verified). It's shown only when present, with a Remove button.
|
|
587
|
+
- **The condition "Aggregate function" option is gone.** Dataverse ignores `aggregate` on a condition and runs it as a plain row condition (verified): FetchXML can't filter on aggregate values. Parsed queries keep it, with a note and a Remove button.
|
|
588
|
+
- The serializer now escapes condition `attribute`, `entityname` and `valueof` values.
|
|
589
|
+
- Removed a leftover "Export not yet implemented" console fallback.
|
|
590
|
+
|
|
591
|
+
### 🔍 New checks
|
|
592
|
+
|
|
593
|
+
Each check was verified against a live Dataverse org.
|
|
594
|
+
- **Errors:**
|
|
595
|
+
- `top` with `count`, `page` or `returntotalrecordcount` (moved from the parser to the query rules, so the panel shows it on the field);
|
|
596
|
+
- `page` below 1;
|
|
597
|
+
- `valueof` with an unsupported operator (`0x80048415`), or with a qualifier the condition can't reach: not a joined link-entity alias or the root entity, or a link-entity joined after the one whose filter holds the condition (`0x80040216`);
|
|
598
|
+
- a link-entity inside a filter that isn't any / not any / all / not all (`0x8004111c`);
|
|
599
|
+
- attributes, all-attributes or orders inside an exists, in, any, not any, all or not all link-entity (`0x80041121`).
|
|
600
|
+
- **Notes (never block):** condition `aggregate`, link-entity `visible`, and `valueof` on operators that take no value.
|
|
601
|
+
|
|
602
|
+
### 🏗️ Technical
|
|
603
|
+
|
|
604
|
+
- Shared panel building blocks: `fields.tsx` (`InfoField`, `InfoCheckbox`, `NumberField`, `LeftoverSetting`), `docLinks.ts`, `editorStyles.ts`, `ruleFindings.ts` and `useEditorMetadata.ts`.
|
|
605
|
+
- `aggregateToggle.ts` plans what turning off aggregate changes; `treeUtils.ts` adds `findNodeContainer`, `collectAllLinkEntities`, `collectAllFilters` and `findOrdersUsingAlias`.
|
|
606
|
+
- Metadata: `IsRetentionEnabled`, `IsLogical` and `IsPrimaryId` are read, and hierarchical entities are detected from their relationships (cached).
|
|
607
|
+
- `attributeCapabilities.ts` adds `getValueOfCompatibleTypes`; `fetchxmlRules.ts` exports `VALUEOF_OPERATORS`, `FILTER_LINK_TYPES`, `NON_RETURNING_LINK_TYPES` and `joinedLinkAliases`.
|
|
608
|
+
|
|
609
|
+
## [1.4.1-beta.8] - 2026-10-14
|
|
610
|
+
|
|
611
|
+
### 🔧 Fixed
|
|
612
|
+
|
|
613
|
+
- **The tool icon now shows in Power Platform ToolBox (#62).** PPTB resolves the top-level `icon` relative to `dist/`, but the SVGs were never copied there, so PPTB showed its default icon. A valid SVG bundled under `dist` is also a **required** item for Verified status ([publishing](https://docs.powerplatformtoolbox.com/tool-development/publishing), [maturity model](https://docs.powerplatformtoolbox.com/tool-development/maturity-model)).
|
|
614
|
+
- The build now bundles the icon at `dist/icons/icon.svg`, and `package.json` points `icon` there.
|
|
615
|
+
- Prerelease versions (e.g. `1.4.1-beta.8`) get the gold-stamped Insider icon; releases get the regular blue icon. There's no manual switch per release.
|
|
616
|
+
|
|
617
|
+
### 🏗️ Technical
|
|
618
|
+
|
|
619
|
+
- **`package-lock.json` is committed**, pinning every dependency with an integrity hash. npm's guidance: it's "intended to be committed into source repositories" and isn't published. PPTB users install the pre-built package, so this only affects development and CI.
|
|
620
|
+
- It enables `npm ci` and `npm audit` (a Verified requirement) and CI (#72).
|
|
621
|
+
- It pins exactly the versions tested so far (restored from the last `npm-shrinkwrap.json`), not a fresh resolution.
|
|
622
|
+
- `files` no longer lists `icons` (the icon ships inside `dist`) or the removed `npm-shrinkwrap.json`.
|
|
623
|
+
|
|
624
|
+
## [1.4.1-beta.7] - 2026-10-14
|
|
625
|
+
|
|
626
|
+
### 🔧 Fixed
|
|
627
|
+
|
|
628
|
+
- **Editor mode: actions now run exactly what's in the editor (#102).** In editor mode, **Execute**, **Save / Save As** and **Send to Tool** first load the editor's FetchXML into the tree, then act on the tree.
|
|
629
|
+
- **What went wrong before:**
|
|
630
|
+
- Execute ran the editor XML, but the results grid took its columns, layout, entity and record actions from the tree. With a different query in the editor, you saw the tree's (often empty) columns.
|
|
631
|
+
- Save As and Send to Tool silently used the tree's XML, not the editor's.
|
|
632
|
+
- Execute, Save As and Send to Tool were disabled while the tree was empty, even with a valid query in the editor.
|
|
633
|
+
- **Now:**
|
|
634
|
+
- Execute is enabled as soon as the editor has content.
|
|
635
|
+
- The tree always matches what ran, was saved or was sent, so the results grid, column picker, sorting and record actions all match too.
|
|
636
|
+
- Editor mode stays on, and your column widths and order are kept. Your text is kept as typed, unless loading changed the query; then the editor shows the cleaned FetchXML that ran, with an "Editor updated" note.
|
|
637
|
+
- When the editor already matches the tree, nothing is reloaded.
|
|
638
|
+
- **No silent changes:** if loading into the tree would change the query (for example, dropping a second `<entity>`), a confirmation lists the changes first ("Load and execute" / "Load and save" / "Load and send"). Invalid FetchXML is reported and nothing runs. Cancel leaves the editor and tree untouched.
|
|
639
|
+
- Loading a different query clears the loaded-view link, as any tree edit does, so Save then saves a new view.
|
|
640
|
+
- **Results are no longer cleared by a fast response** that arrives before the tree update is rendered.
|
|
641
|
+
|
|
642
|
+
### 🏗️ Technical
|
|
643
|
+
|
|
644
|
+
- `editorSync.ts`: a context that lets toolbar buttons rendered inside the results/editor pane ask it to sync before acting.
|
|
645
|
+
- `ParseChangesDialog.tsx`: the Parse to Tree confirmation, shared by Parse to Tree and the editor-mode actions.
|
|
646
|
+
- `loadFetchXml(xml, { preserveLayout })` keeps the current column layout and merges new attributes into it.
|
|
647
|
+
|
|
648
|
+
## [1.4.1-beta.6] - 2026-10-14
|
|
649
|
+
|
|
650
|
+
### 🔧 Fixed
|
|
651
|
+
|
|
652
|
+
- **Validate and Execute now catch the query mistakes Dataverse rejects (#90b, part 1).** New query-level rules, each verified against a live Dataverse org. FXS's parser agreed with Dataverse on all 48 rule cases (0 mismatches).
|
|
653
|
+
- **Aggregate queries:**
|
|
654
|
+
- every attribute needs Group by or an aggregate (`0x8004111e`) and an alias (`0x8004110b`);
|
|
655
|
+
- an attribute can't be both Group by and an aggregate (`0x80041137`);
|
|
656
|
+
- Group by and aggregates outside aggregate queries are errors (`0x8004112e`, `0x8004111a`);
|
|
657
|
+
- an order can only use aliases from its own entity or link-entity (`0x8004110a`);
|
|
658
|
+
- `aggregatelimit` together with an order fails with a SQL error.
|
|
659
|
+
- **Orders:** `entityname` must match a link-entity alias, a link-entity name or the root entity (`0x80041108`), and isn't allowed on orders inside a link-entity.
|
|
660
|
+
- **Aliases:** must be unique across the query, case-insensitively, and can't reuse the logical name of another attribute selected without an alias. Link-entity aliases must be unique too (`0x80041130`).
|
|
661
|
+
- **Filters:** the union hint must be exactly `union`, on an `or` filter, once per query (`0x80041122`). Other hint values were silently dropped by Parse to Tree; they're now reported.
|
|
662
|
+
- **Count child records (`rowaggregate`):** needs an alias, works only on the root entity, and doesn't work in aggregate queries.
|
|
663
|
+
- **Ignored, not errors:** settings Dataverse accepts but ignores are reported as information only. Examples: `aggregatelimit` or date grouping outside aggregate queries, and `distinct` on aggregates other than `countcolumn`.
|
|
664
|
+
- **Boolean spellings Dataverse accepts are read correctly.**
|
|
665
|
+
- `usertimezone="1"` used to be read as **false** (inverted meaning after Parse to Tree).
|
|
666
|
+
- `groupby="1"`, `descending="1"`, `distinct="1"`, `returntotalrecordcount="1"` and `enableprefiltering="1"` (the documented form) were dropped.
|
|
667
|
+
- `descending="True"`, which Dataverse rejects (`0x80041119`), is now an error instead of silently sorting ascending.
|
|
668
|
+
- `aggregate` only makes an aggregate query when it's exactly `"true"`, as Dataverse does.
|
|
669
|
+
- **Report prefiltering is written as `enableprefiltering="1"`**, the documented value ([Use data filtering](https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/analytics/improve-report-performance-by-using-filters?view=op-9-1)).
|
|
670
|
+
- **Choosing "None" no longer writes `aggregate="none"` / `dategrouping="none"` (#88).** The Attribute and Condition editors clear the value instead, and the serializer never writes values outside the documented sets. The invalid "Row Aggregate" aggregate option is removed (`0x8004111a`).
|
|
671
|
+
|
|
672
|
+
### 🏗️ Technical
|
|
673
|
+
|
|
674
|
+
- `model/fetchxmlRules.ts`: query-level rules shared by the parser and (next) the property editors. Findings carry the `nodeId` they refer to.
|
|
675
|
+
- `model/attributeCapabilities.ts`: the metadata-driven capability matrix for the property panel. It records which attribute types support sort, group by and each aggregate function. It came from a 912-query live matrix over every attribute type, identical through `fetchXmlQuery` and `queryData`.
|
|
676
|
+
- Example: Money attributes can't be counted through the Web API.
|
|
677
|
+
- Example: multi-select option sets can't be sorted or grouped.
|
|
678
|
+
- `AttributeNode.aggregate` no longer allows `"rowaggregate"`.
|
|
679
|
+
- 280 unit tests.
|
|
680
|
+
- `npm-shrinkwrap.json` removed, and `finalize-package` no longer runs `npm shrinkwrap` (npm 12 dropped that command). The local `.claude/` workspace folder is git-ignored.
|
|
681
|
+
|
|
682
|
+
## [1.4.1-beta.5] - 2026-10-13
|
|
683
|
+
|
|
684
|
+
### 🔧 Fixed
|
|
685
|
+
|
|
686
|
+
- **FetchXML now runs against the entity's real entity set name (#61).** Queries used to go through the host's `fetchXmlQuery`, which guesses the Web API entity set name with English plural rules. Measured on the sandbox org, the guess is wrong for **67 of 1,612** Advanced Find entities, so every query on them failed. Examples: `feedback` (host requested `feedbacks`) and `msdyn_conversationdata` (actual set `msdyn_conversationsdata`).
|
|
687
|
+
- Execution now uses `dataverseAPI.queryData(`${EntitySetName}?fetchXml=…`)` with the `EntitySetName` from entity metadata (cached). Microsoft requires the entity set name for the entity in the FetchXML's `entity` element ([Retrieve data with FetchXML](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/fetchxml/retrieve-data?tabs=webapi)).
|
|
688
|
+
- The request is otherwise identical: GET with `Prefer: odata.include-annotations="*"`. Results were the same as before across 1,047 audit queries on entities the host names correctly.
|
|
689
|
+
- The entity set comes from the FetchXML being executed, so editor-mode XML and paged requests (Load more, Retrieve all) use the right entity too.
|
|
690
|
+
- Two system entities (`archivecleanupinfo`, `delegatedauthorization`) report an `EntitySetName` the service doesn't route. When Dataverse answers "Resource not found for the segment", the query is retried once with the entity's `LogicalCollectionName`.
|
|
691
|
+
- `fetchXmlQuery` remains only as a fallback when entity metadata can't be loaded.
|
|
692
|
+
- Record delete still goes through the host, which only accepts a logical name. The metadata-based fix needs PPTB 1.2.7's `executeBatch` and moves to the v1.5.0 upgrade (#82).
|
|
693
|
+
|
|
694
|
+
## [1.4.1-beta.4] - 2026-10-13
|
|
695
|
+
|
|
696
|
+
### 📦 Dependencies
|
|
697
|
+
|
|
698
|
+
- **Fluent UI React v9 upgraded (#104)** ahead of the property-panel work in #90b:
|
|
699
|
+
- `@fluentui/react-components` 9.72.4 → 9.74.9;
|
|
700
|
+
- `@fluentui/react-icons` 2.0.313 → 2.0.343;
|
|
701
|
+
- `@fluentui/react-calendar-compat` 0.3.16 → 0.4.7 (required by the date picker);
|
|
702
|
+
- `@fluentui/react-datepicker-compat` 0.6.19 → 0.6.39;
|
|
703
|
+
- `@fluentui/react-timepicker-compat` 0.4.24 → 0.4.41;
|
|
704
|
+
- `@fluentui-contrib/react-data-grid-react-window` 1.4.1 → 1.4.2.
|
|
705
|
+
- Every new version was published at least two weeks ago. `react-icons` is pinned to 2.0.343 instead of 2.0.344 (published 2026-10-09) for that reason.
|
|
706
|
+
- The dependency tree has a single copy of each Fluent package. `npm audit` drops from 29 findings to 26 (three fewer high); the rest are tracked in #77.
|
|
707
|
+
|
|
708
|
+
### 🏗️ Technical
|
|
709
|
+
|
|
710
|
+
- `npm-shrinkwrap.json` was regenerated with npm 11.20.0. npm 12 no longer has `npm shrinkwrap`, and it writes a `package-lock.json` (without integrity hashes) instead of updating the shrinkwrap. Use npm 11 for dependency changes until the release tooling in #75 handles this.
|
|
711
|
+
|
|
712
|
+
## [1.4.1-beta.3] - 2026-10-13
|
|
713
|
+
|
|
714
|
+
### 🔧 Fixed
|
|
715
|
+
|
|
716
|
+
- **Parse to Tree is now lossless (#90a).** Previously the tree model couldn't hold several documented FetchXML attributes, so Parse to Tree (and editing in tree mode) silently dropped them or changed the query's meaning. Measured on all 60 FetchXML examples in the Microsoft Learn FetchXML articles: 25 of them changed after Parse to Tree. Now all 60 round-trip without loss.
|
|
717
|
+
- **`<fetch>`:** `aggregatelimit`, `datasource`, `latematerialize` (was parsed but never written), `options` (query hints, kept exactly as written) and `useraworderby`.
|
|
718
|
+
- **`<attribute>`:** `distinct` (for `countcolumn`) and `rowaggregate="CountChildren"`.
|
|
719
|
+
- **`<order>`:** `alias`, which aggregate queries require.
|
|
720
|
+
- **`<filter>`:** `isquickfindfields`, `overridequickfindrecordlimitenabled` and `overridequickfindrecordlimitdisabled`.
|
|
721
|
+
- **`<entity>`:** `enableprefiltering` and `prefilterparametername` were read but never written.
|
|
722
|
+
- **`<link-entity>`:** empty `from` / `to` are now omitted instead of being written as `from="" to=""`, which made Dataverse silently return no rows. The documented "let Dataverse choose" join for system many-to-many relationships now works after Parse to Tree.
|
|
723
|
+
- **Everything else that works is kept verbatim:** unknown attributes, namespaced attributes, namespace declarations (`xmlns:generator`) and legacy attributes (`version`, `output-format`, `mapping`, `uiname`, `uitype`). A default `xmlns`, which Dataverse rejects, is still removed.
|
|
724
|
+
- **New validation, verified against a live Dataverse environment:**
|
|
725
|
+
- `aggregatelimit` must be 1–50,000;
|
|
726
|
+
- `datasource` must be `retained` (documented) or `bin` (accepted by Dataverse);
|
|
727
|
+
- `aggregate="rowaggregate"` is an error (use `rowaggregate="CountChildren"`);
|
|
728
|
+
- aggregate queries must order by `alias` (`0x80041131`), and non-aggregate queries by `attribute` (`0x80041132`);
|
|
729
|
+
- query hint names that aren't documented get a note, because Dataverse silently ignores them.
|
|
730
|
+
|
|
731
|
+
### 🏗️ Technical
|
|
732
|
+
|
|
733
|
+
- Model: `FetchNode.options` gained `aggregateLimit`, `dataSource`, `useRawOrderBy` and `queryHints` (replacing an unused `hints` object). `AttributeNode` gained `distinct` and `rowAggregate`. `OrderNode.attribute` is now optional, alongside a new `alias`. `FilterNode` gained the quick find flags. Every node gained `extraAttributes` for verbatim pass-through.
|
|
734
|
+
- New round-trip suite (`fetchxmlRoundTrip.test.ts`) over all Learn examples, committed as MIT-licensed fixtures with source URLs (`__fixtures__/learnFetchXml.ts`), plus real-world FetchXML (Advanced Find, saved views, SQL 4 CDS, report prefiltering). 194 unit tests.
|
|
735
|
+
- Live replay: every Learn example executed as written and as regenerated, through the PPTB `dataverseAPI` request path, with identical results for all 53 full queries.
|
|
736
|
+
- Small display fallbacks for alias-only orders: the tree label shows `alias (alias)`, and the grid shows sort arrows on aggregate columns ordered by alias. The full UI controls for the new attributes follow in #90b.
|
|
737
|
+
|
|
738
|
+
## [1.4.1-beta.2] - 2026-10-13
|
|
739
|
+
|
|
740
|
+
### 🔧 Fixed
|
|
741
|
+
|
|
742
|
+
- **Valid FetchXML is no longer blocked in the editor (#89, #48).** Previously *any* parser warning blocked Validate and Execute. That refused FetchXML Dataverse accepts, such as `<fetch xmlns:generator="MarkMpn.SQL4CDS">` from SQL 4 CDS, `useraworderby`, `aggregatelimit` and `<order alias>`.
|
|
743
|
+
- **Principle:** everything the [FetchXML reference](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/reference/) documents is supported. Undocumented behaviour verified to work is supported too. Only what is known not to work is blocked.
|
|
744
|
+
- Every classification was checked against a live Dataverse environment through the PPTB `dataverseAPI` request path: 33 probes, 0 mismatches.
|
|
745
|
+
- **Errors** (Execute is blocked) are things Dataverse is known to reject:
|
|
746
|
+
- invalid attribute alias characters;
|
|
747
|
+
- an unknown child element;
|
|
748
|
+
- non-integer `top` / `count` / `page`;
|
|
749
|
+
- `top` combined with `count`, `page` or `returntotalrecordcount`;
|
|
750
|
+
- an unknown `link-type`;
|
|
751
|
+
- an invalid filter `type`, aggregate or date grouping;
|
|
752
|
+
- a missing `name` / `attribute` / `operator`;
|
|
753
|
+
- an order with neither attribute nor alias;
|
|
754
|
+
- a default `xmlns`.
|
|
755
|
+
|
|
756
|
+
Parse to Tree can still build the tree and lists what it corrects or removes. Malformed XML, a missing `<entity>`, and a link-entity with empty `from=""` and `to=""` (Dataverse silently returns no rows) can't be parsed at all.
|
|
757
|
+
- **Warnings** (with **Execute anyway**) are only for what can't be determined in the client: a link-entity without `from` / `to` (valid only for system many-to-many relationships), and an operator that isn't in the documented list.
|
|
758
|
+
- **Notes** never block:
|
|
759
|
+
- namespace declarations, namespaced attributes and unknown attributes (Dataverse ignores them);
|
|
760
|
+
- documented attributes the visual builder doesn't support yet, including `datasource`;
|
|
761
|
+
- `top` / `count` above 5,000 (Dataverse returns at most 5,000 rows);
|
|
762
|
+
- the deprecated `neq` operator (works like `ne`);
|
|
763
|
+
- extra `<entity>` elements (Dataverse uses the first).
|
|
764
|
+
|
|
765
|
+
Common legacy attributes from Advanced Find and saved views (`version`, `output-format`, `mapping`, `uiname`, `uitype`) are accepted silently.
|
|
766
|
+
- **`link-type="natural"` is supported.** It's undocumented, but Dataverse accepts it; it's listed in Dataverse's own link-type error and was verified against a live org. It's preserved through Parse to Tree.
|
|
767
|
+
- **Parse to Tree no longer changes queries silently.** When it would remove or rewrite something, a confirmation dialog lists the changes under **Removed** and **Corrected**. Examples: `datasource="retained"`, which would otherwise read live data; `<order alias>`; an invalid alias.
|
|
768
|
+
- Order elements that the tree can't represent are now dropped, with a note, instead of being rewritten as the invalid `<order attribute="" />`.
|
|
769
|
+
- The misleading "Auto-corrected" label is gone.
|
|
770
|
+
- **Validate** shows at most one bar per level (Invalid FetchXML / "Dataverse might reject this query" / Notes) instead of one bar per finding. Lists are bulleted.
|
|
771
|
+
|
|
772
|
+
### 🏗️ Technical
|
|
773
|
+
|
|
774
|
+
- `ParseWarning` gained `severity` (`error` | `warning` | `info`) and `kind` (`invalid` | `ignored` | `dropped` | `corrected` | `advice`).
|
|
775
|
+
- New helpers: `getExecutionErrors()`, `getBlockingWarnings()` and `getParseToTreeChanges()`.
|
|
776
|
+
- Unknown-attribute checks now cover every element, using per-element attribute lists from the reference.
|
|
777
|
+
- New `ParseIssueList` component. The Parse to Tree confirmation uses a Fluent UI v9 `Dialog` (`modalType="alert"`, Cancel focused first).
|
|
778
|
+
- 44 new unit tests, 109 in total.
|
|
779
|
+
|
|
780
|
+
## [1.4.1-beta.1] - 2026-10-12
|
|
781
|
+
|
|
782
|
+
### 🔧 Fixed
|
|
783
|
+
|
|
784
|
+
- **Wrong attribute injected into the root entity (#54).** The root table was taken from the first entry of an alphabetically sorted list of the query's tables. That list was sorted *in place*, so a link-entity whose name sorts first (e.g. `bookableresource` before `msdyn_timeentry`) was treated as the root. Its primary key (`bookableresourceid`) was injected into the root `<entity>`, and its metadata also drove relationships, the layout and bulk delete.
|
|
785
|
+
- The root now always comes from the query's `<entity>` element.
|
|
786
|
+
- Metadata responses that arrive after the query changed are discarded.
|
|
787
|
+
- Root metadata is only used when its `LogicalName` matches the current root.
|
|
788
|
+
- **FetchXML is no longer modified (#59).** The primary key used to be injected into the generated FetchXML. That broke aggregate queries (`0x8004111e: An attribute can not be requested when an aggregate operation has been specified…`), silently defeated `distinct`, and the injected attribute never appeared in the tree.
|
|
789
|
+
- Validated on a live org through the PPTB `dataverseAPI` request path: Dataverse returns the root primary key for every non-aggregate, non-distinct query without it being selected ([Select columns](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/select-columns?tabs=webapi)). Distinct queries return none ([Ordering and paging](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/page-results?tabs=webapi#ordering-and-paging)).
|
|
790
|
+
- What you see in the FetchXML pane, copy, save as a view, send to another tool or generate code from is now exactly what you built.
|
|
791
|
+
- **Record actions use real record IDs only.**
|
|
792
|
+
- Record actions (Open, Copy URL, Delete, Bulk Delete, Run Workflow) are enabled only when the results contain the table's primary key.
|
|
793
|
+
- For aggregate and distinct results they are disabled, and a "Record actions unavailable" indicator explains why. Previously, Open/Copy URL could be clicked for an aggregate row and used a random ID.
|
|
794
|
+
- Grid row IDs are now unique and stable even when a 1:N join repeats a record. Synthetic row IDs are never passed to record actions.
|
|
795
|
+
|
|
796
|
+
### 🏗️ Technical
|
|
797
|
+
|
|
798
|
+
- `generateFetchXml(fetchNode)` no longer takes a `primaryIdAttribute` parameter.
|
|
799
|
+
- New `getQueryEntities()` (`model/treeUtils.ts`) and `buildRowIdentity()` (`ui/RightPane/rowIdentity.ts`), with unit tests: 65 tests in total.
|
|
800
|
+
- **Unit test baseline (#64).** Added Vitest (`npm test`, `npm run test:watch`, `npm run coverage`) with jsdom, plus 52 characterization tests for the FetchXML serializer, parser (including a parse → generate → parse round-trip of the #54 query), paging-attribute rewriting, alias utilities and the operator catalogue.
|
|
801
|
+
- **Lint cleanup, part 1 (#65).** ESLint errors reduced to zero with no behaviour change:
|
|
802
|
+
- `useBuilder` / `BuilderContext` moved to `state/builderContext.ts`;
|
|
803
|
+
- `useTheme` / `ThemeContext` moved to `shared/contexts/useTheme.ts`;
|
|
804
|
+
- `getCellRenderer` moved to `RightPane/getCellRenderer.tsx` (React Fast Refresh: component modules export only components);
|
|
805
|
+
- removed two `any` types in `ResultsGrid.tsx`;
|
|
806
|
+
- `_`-prefixed bindings are now allowed as intentionally unused.
|
|
807
|
+
- The remaining `react-hooks/exhaustive-deps` warnings can affect runtime behaviour and are handled separately.
|
|
808
|
+
|
|
809
|
+
## [1.4.0] - 2026-07-01
|
|
810
|
+
|
|
811
|
+
### ✨ Added
|
|
812
|
+
|
|
813
|
+
#### Tool-to-Tool (T2T) invocation
|
|
814
|
+
|
|
815
|
+
FetchXML Studio now participates in PPTB Tool-to-Tool (T2T) invocation in both directions. This feature was iterated through the `1.2.2-beta.*` and `1.2.3-beta.*` prereleases (detailed below); `1.4.0` is its first stable release. All T2T behavior is capability-gated and feature-detected, so standalone use and hosts without invocation/discovery support are unaffected. `features.minAPI` stays `1.2.2`.
|
|
816
|
+
|
|
817
|
+
- **Send to Tool (caller)** — Send the current FetchXML (plus root entity) to another PPTB tool. Targets are discovered automatically via the capability registry (`invocation.findToolsByCapability("fetchxml")`); the "Send to Tool" dropdown lists installed tools that declare the `fetchxml` capability, excluding FetchXML Studio itself (matched on the runtime tool id from `getToolContext()`). One-way, fire-and-forget handoff via `launchTool(..., { noReturn: true })`.
|
|
818
|
+
- **Callee (inbound prefill + return)** — When launched by another tool, FetchXML Studio consumes the inbound launch context to pre-populate the builder (resolution priority `fetchXml` → `viewRef` → `entityLogicalName`; the root entity is always derived from the FetchXML). A "Return FetchXML" button (shown only when launched as a callee) returns `{ fetchXml }` to the caller via `returnData()`.
|
|
819
|
+
- **Contract** — `pptb.config.json` declares `capabilities: ["fetchxml"]`, a `prefill` schema, and a `returnTopic`.
|
|
820
|
+
|
|
821
|
+
### 🏗️ Technical
|
|
822
|
+
|
|
823
|
+
- Depends on `@pptb/types` `1.2.3` (stable). New modules: `src/features/fetchxml/api/invocation.ts`, `src/shared/hooks/useLaunchContext.ts`, `src/shared/hooks/useSendToTools.ts`, `src/features/fetchxml/ui/Toolbar/SendToToolButton.tsx`. See the `1.2.x-beta.*` entries below for the full iteration history.
|
|
824
|
+
|
|
825
|
+
## [1.3.1] - 2026-05-30
|
|
826
|
+
|
|
827
|
+
### 🔧 Fixed
|
|
828
|
+
|
|
829
|
+
- **Double sort arrows on results grid column headers** (#22, #35) — The Fluent UI v9 `DataGrid` component renders its own sort indicator when `sortable` is set without a controlled `sortState`. This caused both Fluent's built-in arrow and the custom `renderHeaderCell` multi-sort arrow to render simultaneously. Fixed by passing a controlled `sortState` sentinel value (`sortColumn: "__sort_managed_externally__"`) that never matches any real column ID, preventing Fluent from rendering its internal indicator. The custom FetchXML-driven sort arrows in the column headers are now the sole source of truth.
|
|
830
|
+
|
|
831
|
+
- **"Parse to Tree" injects incorrect primary key for activity entities** (#37) — Standard entities follow the `${entityname}id` naming convention (e.g. `accountid`, `contactid`), but activity entities (`email`, `task`, `phonecall`, `fax`, `letter`, `appointment`, and custom activities) all inherit from `activitypointer` and use `activityid` as their primary key. The previous code hardcoded the `${entityname}id` pattern everywhere, causing `emailid`, `taskid`, etc. to be injected into the FetchXML query tree — attributes that do not exist in Dataverse. Fixed by threading `EntityMetadata.PrimaryIdAttribute` (the authoritative value from the Dataverse metadata API) through the full call chain. No fallback pattern or guessing — the primary key is only injected when the metadata is available.
|
|
832
|
+
|
|
833
|
+
### 🏗️ Technical
|
|
834
|
+
|
|
835
|
+
- `fetchxml.ts` — `generateFetchXml(fetchNode, primaryIdAttribute?)` accepts the PK from the caller; only injects the primary ID attribute when explicitly provided.
|
|
836
|
+
- `layoutxml.ts` — `generateLayoutFromFetchXml(fetchQuery, primaryIdAttribute?, attributeTypeMap?)` — PK is now optional; stored in `LayoutXmlConfig.primaryIdAttribute` when provided.
|
|
837
|
+
- `builderStore.tsx` — `ViewLoadInfo` now includes `originalFetchXml: string`; `SYNC_LAYOUT_WITH_FETCHXML` action and `syncLayoutWithFetchXml()` both accept `primaryIdAttribute?`; the store no longer derives or guesses the primary key itself.
|
|
838
|
+
- `ResultsGrid.tsx` — `primaryIdAttribute` prop replaces the old `${entityName}id` `useMemo`; `sortState` sentinel eliminates the double-arrow rendering.
|
|
839
|
+
- `PreviewTabs.tsx` — threads `primaryIdAttribute?` down to `ResultsGrid`.
|
|
840
|
+
- `AppShell.tsx` — passes `entityMetadata.PrimaryIdAttribute` to all call sites: `generateFetchXml`, `syncLayoutWithFetchXml`, `setLoadedView` (via `originalFetchXml`), and `<PreviewTabs>`.
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## [1.3.0] - 2026-05-29
|
|
845
|
+
|
|
846
|
+
### ✨ Added
|
|
847
|
+
|
|
848
|
+
#### Code Generation Panel (#38)
|
|
849
|
+
- **Code tab** — New tab in the right pane (alongside FetchXML, LayoutXML, Results) with a tabbed panel that generates ready-to-use code from the current query in seven formats. Async formats (C# QueryExpression, SQL) run in parallel and cancel automatically when the query changes.
|
|
850
|
+
|
|
851
|
+
#### C# QueryExpression (#39, #40)
|
|
852
|
+
- Converts FetchXML to a full `QueryExpression` SDK object graph via the Dataverse `FetchXmlToQueryExpression` Web API function.
|
|
853
|
+
- Emits `ColumnSet`, `FilterExpression`, `ConditionExpression`, `LinkEntity`, `OrderExpression`, `XrmAttributeExpression`, `PagingInfo`, `TopCount`, `Distinct`, and `NoLock`.
|
|
854
|
+
- Full aggregate query support — `XrmAggregateType` (Count, CountColumn, Sum, Avg, Min, Max) and `XrmDateTimeGrouping` (Day, Week, Month, Quarter, Year, FiscalPeriod, FiscalYear).
|
|
855
|
+
- Correctly resolves all Dataverse API enum values returned as strings (`ConditionOperator`, `JoinOperator`, `LogicalOperator`, `OrderType`, `XrmAggregateType`, `XrmDateTimeGrouping`) via a shared `resolveEnum()` helper.
|
|
856
|
+
- Correctly unwraps .NET-serialized condition values (`{ "Type": "System.Int32", "Value": 0 }`) to C# integer / string / GUID literals via `unwrapODataValue()`.
|
|
857
|
+
|
|
858
|
+
#### C# FetchExpression (#43)
|
|
859
|
+
- Wraps the FetchXML in `new FetchExpression(@"...")` using a C# verbatim string literal for multi-line XML.
|
|
860
|
+
|
|
861
|
+
#### JavaScript (#43)
|
|
862
|
+
- Generates an `Xrm.WebApi.retrieveMultipleRecords` call for model-driven app JS, PCF components, and browser console.
|
|
863
|
+
|
|
864
|
+
#### pac CLI (#43)
|
|
865
|
+
- Generates a `pac org fetch --xml "..."` command for the Power Platform CLI.
|
|
866
|
+
|
|
867
|
+
#### Power Automate (#43)
|
|
868
|
+
- Field-by-field form mirroring the Dataverse "List rows" connector UI — not a plain code dump.
|
|
869
|
+
- Displays Table name, Select Columns, Filter Rows, Sort By, Top Count, and Fetch Xml Query fields with individual copy buttons and flash confirmation feedback.
|
|
870
|
+
- Table name resolved from `EntityDefinitions` metadata (already cached from entity selection) — accurate OData entity set name, not a heuristic pluralisation.
|
|
871
|
+
|
|
872
|
+
#### Web API (#43)
|
|
873
|
+
- Full OData URL with URL-encoded FetchXML, required request headers, a `curl` snippet, and a PowerShell `Invoke-RestMethod` example.
|
|
874
|
+
- Entity set name resolved from the same metadata cache as the Power Automate tab.
|
|
875
|
+
|
|
876
|
+
#### SQL (#39)
|
|
877
|
+
- T-SQL generated via the Dataverse `FetchXMLToSQL` Web API function (undocumented — labelled "Preview" with a tooltip warning).
|
|
878
|
+
- Robust response-property fallback parsing handles multiple possible response key names.
|
|
879
|
+
|
|
880
|
+
### 🏗️ Technical
|
|
881
|
+
|
|
882
|
+
- Added `src/features/fetchxml/engine/` — new code-generation module:
|
|
883
|
+
- `queryExpressionTypes.ts` — TypeScript interfaces mirroring the Dataverse QE JSON schema; all enum fields typed as `number | string` to match actual API wire responses.
|
|
884
|
+
- `queryExpressionCodegen.ts` — C# SDK code emitter with `resolveEnum()` and `unwrapODataValue()` helpers for robust handling of string-name enums and .NET-serialized value wrappers.
|
|
885
|
+
- `fetchxmlCodeGenerators.ts` — Sync code generators (FetchExpression, JavaScript, pac CLI, Power Automate spec, Web API); pure functions with no API calls required.
|
|
886
|
+
- Added `src/features/fetchxml/ui/RightPane/PowerAutomatePane.tsx` — field-by-field form component (3-column grid: label | input | copy button) with "N of N populated" badge and "Copy all" action.
|
|
887
|
+
- Added `src/features/fetchxml/ui/RightPane/CodePanel.tsx` — 7-tab code generation panel; async tabs run in parallel with abort-on-change cancellation.
|
|
888
|
+
- `PreviewTabs.tsx` — Added Code tab with `Code20Regular` icon.
|
|
889
|
+
- `pptbClient.ts` — Added `fetchXmlToQueryExpression` (calls `FetchXmlToQueryExpression` Dataverse function) and `fetchXmlToSQL` (calls `FetchXMLToSQL`) with multi-property fallback response parsing.
|
|
890
|
+
|
|
891
|
+
## [1.2.3-beta.3] - 2026-07-01
|
|
892
|
+
|
|
893
|
+
### 🏗️ Technical
|
|
894
|
+
|
|
895
|
+
- Upgraded `@pptb/types` `1.2.3-beta.0` → `1.2.3` (stable) ahead of merging the T2T branch into `dev`. Types are compile-time only, so there is **no runtime change** — the API surface FetchXML Studio uses (capability discovery, `launchTool` `noReturn`, `getToolContext`) is identical in the stable release. `features.minAPI` stays at `1.2.2`; discovery remains feature-detected and degrades gracefully on older hosts.
|
|
896
|
+
|
|
897
|
+
## [1.2.3-beta.2] - 2026-07-01
|
|
898
|
+
|
|
899
|
+
### 🐛 Fixed
|
|
900
|
+
|
|
901
|
+
- **Send to Tool no longer spins forever.** The launch is now fire-and-forget — previously the button awaited `launchTool`, whose promise doesn't resolve until the callee window closes, so the spinner stayed up for the whole session (reported when sending FXS → Bulk Data Studio). This is purely a caller-side fix; a callee does **not** need to declare a `returnTopic` to avoid it.
|
|
902
|
+
- **Always shows a "Send to Tool" dropdown.** Previously a single discovered tool collapsed the toolbar control into a button labelled with that tool's name; it now consistently reads "Send to Tool" and lists the target(s) in the menu.
|
|
903
|
+
- **Each target shows its tool id on its own line** beneath the display name in the menu.
|
|
904
|
+
- **FetchXML Studio no longer lists itself** as a target. Self-exclusion now matches on the runtime tool id from `getToolContext()` (with the npm package id as a fallback), since discovery reports the runtime id (e.g. `npm-mohsinonxrm-pptb-fetchxml-studio`) rather than the npm package name.
|
|
905
|
+
|
|
906
|
+
## [1.2.3-beta.1] - 2026-06-17
|
|
907
|
+
|
|
908
|
+
### ✨ Added
|
|
909
|
+
|
|
910
|
+
#### Tool-to-Tool (T2T) — capability-based tool discovery
|
|
911
|
+
|
|
912
|
+
The "Send to Tool" target list is now populated automatically via the PPTB capability registry instead of a manually maintained list. Requires **`@pptb/types` ≥ 1.2.3-beta.0** and a host that exposes capability discovery.
|
|
913
|
+
|
|
914
|
+
- **Automatic discovery** — FetchXML Studio calls `toolboxAPI.invocation.findToolsByCapability("fetchxml")` to find installed tools that declare the `fetchxml` capability, excludes itself, and lists them in the Send to Tool button (single button for one match, dropdown for several). Hidden when no matches or when the host lacks discovery — feature-detected at runtime, so older hosts degrade gracefully.
|
|
915
|
+
- **One-way send** — Launches now pass `noReturn: true`, the dedicated flag for the "Send To" pattern, which suppresses the callee's "Return to [Caller]" banner since FXS isn't waiting for data back.
|
|
916
|
+
|
|
917
|
+
### 🔧 Changed
|
|
918
|
+
|
|
919
|
+
- **Removed the manual "Tool Integration" settings.** The Settings → Tool Integration section and the `targetTools` display setting are gone; discovery replaces them entirely.
|
|
920
|
+
|
|
921
|
+
### 🏗️ Technical
|
|
922
|
+
|
|
923
|
+
- Bumped `@pptb/types` `1.2.2-beta.1` → `1.2.3-beta.0`, which now types `invocation.findToolsByCapability` / `getKnownCapabilityTags`, the `capabilities` field on `InvocationConfig`, the `CapabilityTag`/`KnownCapabilityTag` types, and `launchTool`'s `noReturn` option. Removed the local `InvocationAPI` interface in `invocation.ts` in favor of the now-published host types.
|
|
924
|
+
- `src/features/fetchxml/api/invocation.ts` — Added `DiscoveredTool` + `discoverFetchXmlTools()` (feature-detected; filters out our own id; defaults `name` to `id`); added `noReturn: true` to `sendFetchXmlToTool`.
|
|
925
|
+
- `src/shared/hooks/useSendToTools.ts` — New hook; discovers send targets once on mount.
|
|
926
|
+
- `src/app/AppShell.tsx` — Uses `useSendToTools`; Send to Tool slot now gated on `isT2TSupported() && sendToTools.length > 0`.
|
|
927
|
+
- `src/features/fetchxml/ui/Toolbar/SendToToolButton.tsx` — Prop changed from `targetTools: string[]` to `tools: DiscoveredTool[]`; labels use the discovered tool name.
|
|
928
|
+
- `src/features/fetchxml/ui/Settings/SettingsDrawer.tsx` — Removed the Tool Integration section, its handlers/state/styles, and now-unused imports.
|
|
929
|
+
- `src/features/fetchxml/model/displaySettings.ts` — Removed `targetTools` from `DisplaySettings` and the defaults.
|
|
930
|
+
|
|
931
|
+
## [1.2.2-beta.2] - 2026-06-16
|
|
932
|
+
|
|
933
|
+
### ✨ Added
|
|
934
|
+
|
|
935
|
+
#### Tool-to-Tool (T2T) Invocation — Callee (prefill + return)
|
|
936
|
+
|
|
937
|
+
FetchXML Studio can now be *launched by* another PPTB tool, pre-populated with an inbound query, and hand a query back. Completes the round-trip alongside the existing caller support.
|
|
938
|
+
|
|
939
|
+
- **Inbound prefill** — On launch via T2T, FetchXML Studio reads its launch context and pre-populates the builder. Resolution priority is `fetchXml` → `viewRef` → `entityLogicalName`: a `fetchXml` string is loaded directly; a `viewRef` has its FetchXML retrieved from the referenced `savedquery`/`userquery`; and a bare `entityLogicalName` starts a fresh query on that entity. When `fetchXml` is present the root entity is **derived from the FetchXML itself** and any `entityLogicalName` hint is ignored, because a mismatched value would break this metadata-driven tool's lookups.
|
|
940
|
+
- **Return FetchXML button** — When launched as a callee, a "Return FetchXML" button appears in the FetchXML toolbar. It returns `{ fetchXml }` (the current editor-or-tree query) to the caller via `returnData()`, which the host resolves as the caller's `launchTool(...)` result. This is intentionally separate from the host-injected "Return to [Caller]" banner, which only navigates back and resolves the caller's promise with `null`.
|
|
941
|
+
- **`pptb.config.json`** — Added the `fetchxml` capability tag (for discovery via `findToolsByCapability`) and a `returnTopic` describing the `{ fetchXml }` return shape. Relaxed the prefill contract so `entityLogicalName` is optional/hint-only.
|
|
942
|
+
|
|
943
|
+
### 🏗️ Technical
|
|
944
|
+
|
|
945
|
+
- `src/features/fetchxml/api/invocation.ts` — Added callee helpers: `getLaunchContext()`, `resolveLaunchPrefill(context)` (returns a `{ kind: "fetchxml" | "entity" }` action following the `fetchXml` → `viewRef` → `entityLogicalName` priority; resolves `viewRef` by reusing the `dataverseAPI.queryData` view-read route filtered to a single record), `returnDataToInvokingTool(data)`, and `returnFetchXmlToInvokingTool(fetchXml)`. Added `FetchXmlStudioLaunchPrefill` type and `LaunchPrefillAction` union.
|
|
946
|
+
- `src/shared/hooks/useLaunchContext.ts` — New hook. Calls `getLaunchContext()` once on mount and exposes `{ loading, isCallee, context }` so detection happens in one place.
|
|
947
|
+
- `src/app/AppShell.tsx` — Consumes the prefill exactly once on launch (loads incoming FetchXML via `builder.loadFetchXml`), and passes `isCallee` to `PreviewTabs`.
|
|
948
|
+
- `src/features/fetchxml/ui/RightPane/PreviewTabs.tsx` — Added `isCallee?` prop and a `ReturnFetchXmlButton` (gated on `isCallee`) rendered next to Execute; uses a `getCurrentXml()` helper that returns the live editor buffer when in editor mode.
|
|
949
|
+
|
|
950
|
+
### 🔧 Changed
|
|
951
|
+
|
|
952
|
+
- **"Send to Tool" button visibility** — The caller button is now hidden entirely unless at least one target tool is configured under Settings → Tool Integration (previously it rendered as a disabled button with a tooltip). The two T2T toolbar actions are now strictly capability-gated and never change meaning by mode: **Return FetchXML** shows only when launched as a callee (`isCallee`); **Send to Tool** shows only when targets are configured.
|
|
953
|
+
|
|
954
|
+
## [1.2.2-beta.1] - 2026-05-25
|
|
955
|
+
|
|
956
|
+
### ✨ Added
|
|
957
|
+
|
|
958
|
+
#### Tool-to-Tool (T2T) Invocation — Caller
|
|
959
|
+
|
|
960
|
+
FetchXML Studio can now send the active query to any other PPTB tool that accepts a FetchXML prefill payload. Requires **PPTB host ≥ 1.2.2**.
|
|
961
|
+
|
|
962
|
+
- **Send to Tool button** — A new "Send to Tool" button appears in the toolbar (alongside Save View) whenever the PPTB host exposes the `toolboxAPI.invocation` API. Three interaction modes depending on how many target tools are configured:
|
|
963
|
+
- *No tools configured* — Button is disabled with a tooltip pointing to Settings → Tool Integration.
|
|
964
|
+
- *One tool configured* — Single button; click launches the tool directly.
|
|
965
|
+
- *Multiple tools configured* — Dropdown menu listing all configured tools; click an item to launch.
|
|
966
|
+
- **Settings → Tool Integration section** — New section in the Settings drawer to manage the list of target tool npm package IDs (e.g. `@linked365/pptb-bulk-data-studio`). Supports add (text input + Enter/Add button) and remove (× button per item). The list is persisted via `toolboxAPI.settings` alongside other display preferences.
|
|
967
|
+
- **Active connection forwarding** — The caller automatically retrieves the currently active Dataverse connection and forwards its ID as `primaryConnectionId` when launching the target tool, so the callee opens against the same environment.
|
|
968
|
+
- **`pptb.config.json`** — Added PPTB callee contract file at the repository root. Declares the prefill schema this tool accepts when *receiving* a T2T invocation from another tool (see [Callee Contract](#-callee-contract-pptbconfigjson) in the README). `viewRef` is modelled as a Dataverse EntityReference: required fields `id` (UUID string) and `entityLogicalName` (enum: `"savedquery"` | `"userquery"`).
|
|
969
|
+
|
|
970
|
+
### 🏗️ Technical
|
|
971
|
+
|
|
972
|
+
- `src/features/fetchxml/api/invocation.ts` — New module. Defines a local `InvocationAPI` interface (mirrors the PPTB host API not yet in `@pptb/types`) and exposes `isT2TSupported()` and `sendFetchXmlToTool(targetToolId, { fetchXml, entityLogicalName })`.
|
|
973
|
+
- `src/features/fetchxml/ui/Toolbar/SendToToolButton.tsx` — New toolbar component. Handles the disabled / single / multi-tool rendering variants; calls `sendFetchXmlToTool`; surfaces errors via `toolboxAPI.utils.showNotification`.
|
|
974
|
+
- `src/features/fetchxml/model/displaySettings.ts` — Added `targetTools: string[]` (default `[]`) to `DisplaySettings` interface and `defaultDisplaySettings`.
|
|
975
|
+
- `src/features/fetchxml/ui/Settings/SettingsDrawer.tsx` — Added Tool Integration section with add/remove UI. Added `Input`, `Field`, `Add20Regular`, `Dismiss16Regular`, `PlugConnected20Regular` to imports.
|
|
976
|
+
- `src/features/fetchxml/ui/RightPane/PreviewTabs.tsx` — Added `sendToToolButton?: ReactNode` prop; rendered unconditionally (all tabs) next to `saveViewButton`.
|
|
977
|
+
- `src/app/AppShell.tsx` — Imports `SendToToolButton` + `isT2TSupported`; passes `sendToToolButton` prop to `PreviewTabs` gated on `isT2TSupported()`.
|
|
978
|
+
- `package.json` — `features.minAPI` bumped from `1.2.0` → `1.2.2` to reflect the new PPTB host requirement. `scheduler` added to `devDependencies` (required peer dep of `@fluentui/react-context-selector` that was missing from the install tree).
|
|
979
|
+
|
|
980
|
+
#### Packaging fixes (resolves #33)
|
|
981
|
+
|
|
982
|
+
- **`icon` path corrected** — `"icon"` field in `package.json` was missing the `icons/` directory prefix (`"icon-insider.svg"` → `"icons/icon-insider.svg"`). Icon was not resolvable by PPTB at install time.
|
|
983
|
+
- **`files` array expanded** — Added `"icons"`, `"index.html"`, `"LICENSE"`, `"README.md"`, `"CHANGELOG.md"`, `"SECURITY.md"` alongside the existing `"dist"` and `"npm-shrinkwrap.json"` entries. Previously the `icons/` folder (referenced by the `"icon"` field) and all root-level docs were absent from the published package.
|
|
984
|
+
- **Production source maps disabled** — Added `sourcemap: false` to `vite.config.ts` `build` options. Monaco worker source maps were silently adding ~17 MB to the package; disabling them brings the published size down to ~3–4 MB.
|
|
985
|
+
|
|
986
|
+
---
|
|
987
|
+
|
|
988
|
+
## [1.2.1] - 2026-04-07
|
|
989
|
+
|
|
990
|
+
### ✨ Added
|
|
991
|
+
|
|
992
|
+
- **Copy button in editor mode** — The FetchXML editor toolbar now includes a Copy button when in editor mode, making it consistent with the read-only toolbar. Previously Copy was only available when the editor was not in edit mode.
|
|
993
|
+
|
|
994
|
+
### 🔧 Fixed
|
|
995
|
+
|
|
996
|
+
- **Results grid column overlap** — Column widths are now estimated dynamically from header text (capped at 400 px) so adjacent columns no longer overlap on first render. All cells use `TableCellLayout truncate` for clean overflow.
|
|
997
|
+
- **Results grid header/row misalignment** — Removed a stray CSS class from `DataGridBody` that was causing header and row widths to drift out of sync when the grid had overflowing content.
|
|
998
|
+
- **Alias validation in property editors** — Typing an invalid alias character (space, hyphen, special character) in `AttributeEditor` or `LinkEntityEditor` now sanitizes the value in real time: invalid characters are stripped, and a leading digit is prefixed with `_`.
|
|
999
|
+
- **Alias validation in parser** — When parsing FetchXML (both *Parse to Tree* and *Execute*), invalid aliases are now automatically sanitized. A parse warning describes the correction (e.g. *"Alias 'this is id' had invalid characters and was corrected to 'thisisid'."*) instead of silently forwarding the invalid alias to Dataverse where it would cause an API error.
|
|
1000
|
+
- **Execute blocked when alias warnings are present** — Clicking Execute in editor mode now validates the XML completely before sending it to Dataverse. If parse warnings exist (e.g. an invalid alias that wasn't auto-corrected via *Parse to Tree*), execution is blocked and the warning is surfaced in a message bar above the editor with a hint to use *Parse to Tree* to auto-correct. Previously the bad alias was silently sent to Dataverse and the error appeared in the Results tab.
|
|
1001
|
+
- **Editor state preserved across tab switches** — Switching from the FetchXML tab to Results or LayoutXML and back no longer resets editor mode or discards the edited XML. The editor component is now always mounted (hidden via `display: none` when not active) so Monaco state — editor mode toggle, cursor position, edit buffer — is fully preserved.
|
|
1002
|
+
- **Validate contradictory messages** — *Validate* no longer shows both a "Valid" success message and warnings simultaneously. When warnings are present, validate now shows each warning plus an "Action required" notice and stops — the "Valid" success message is only shown when there are zero warnings.
|
|
1003
|
+
- **Parse to Tree corrections shown as informational** — Auto-corrected alias warnings from *Parse to Tree* are now shown with `intent="info"` (blue) rather than `intent="warning"` (yellow), correctly communicating that the tool has already resolved the problem.
|
|
1004
|
+
- **Execute API errors surfaced** — Dataverse API errors that occur during query execution are now captured and displayed in a message bar in the Results tab. Previously they were only visible in the browser console.
|
|
1005
|
+
|
|
1006
|
+
### 🏗️ Technical
|
|
1007
|
+
|
|
1008
|
+
- Added `src/features/fetchxml/model/aliasUtils.ts` — `isValidAlias()` and `sanitizeAlias()` shared utilities enforcing the Dataverse alias constraint (`[A-Za-z_][A-Za-z0-9_]*`).
|
|
1009
|
+
- `fetchxmlParser.ts` — `parseAttribute` and `parseLinkEntity` now import `sanitizeAlias`; invalid aliases are corrected in place and described in the parse warning rather than propagated as-is.
|
|
1010
|
+
- `PreviewTabs.tsx` — `FetchXmlEditor` is always mounted; `handleExecute` blocks on parse warnings; navigating back to the FetchXML tab clears any stale editor validation error banner.
|
|
1011
|
+
- `AppShell.tsx` — `handleExecute` accepts an optional `xmlOverride` (used when editor mode is active); `executeError` state threads Dataverse API errors back to the `PreviewTabs` message bar.
|
|
1012
|
+
|
|
1013
|
+
---
|
|
1014
|
+
|
|
1015
|
+
## [1.2.0] - 2026-04-03
|
|
1016
|
+
|
|
1017
|
+
### ✨ Added
|
|
1018
|
+
|
|
1019
|
+
#### Query Scope Settings
|
|
1020
|
+
- **Entity Scope Mode** — New setting (persisted across sessions) to control which entities appear in the entity dropdown:
|
|
1021
|
+
- **Publisher + Solution** — Select a publisher, then narrow to specific solutions; only entities belonging to those solutions are shown
|
|
1022
|
+
- **Solution Only** — Skip the publisher step and pick solutions directly
|
|
1023
|
+
- **All Entities** — Show every entity in the environment (no solution filter)
|
|
1024
|
+
- **Advanced Find Only toggle** — When enabled (default), entities and attributes are limited to those marked `IsValidForAdvancedFind = true`, matching the classic Advanced Find behavior. Disabling shows all entities and attributes — including system and developer-only tables — useful for building admin or integration queries. The filter is applied locally so toggling is instant with no additional API call.
|
|
1025
|
+
- Scope mode and Advanced Find preference are persisted individually via `toolboxAPI.settings` and survive across sessions.
|
|
1026
|
+
|
|
1027
|
+
#### Monaco Editor — Local Bundling
|
|
1028
|
+
- Monaco is now fully bundled in `dist/` instead of being fetched from a CDN at runtime.
|
|
1029
|
+
- Eliminates CSP errors and ensures line numbers, syntax highlighting, and all editor features work correctly in the PPTB host environment without network access to external CDNs.
|
|
1030
|
+
|
|
1031
|
+
### 🔧 Fixed
|
|
1032
|
+
|
|
1033
|
+
- **Internal solutions in Solution-only mode** — Solutions from `isreadonly = true` publishers (Microsoft, system publishers) are now excluded from the solution picker, consistent with what Publisher + Solution mode shows. Previously some Microsoft-owned visible solutions appeared in the list.
|
|
1034
|
+
- **Parse-to-tree no longer resets the entity** — Pasting FetchXML into the editor and clicking "Parse to Tree" no longer wipes the selected entity. The validation that auto-clears the entity selection now only fires when there is an active solution filter in place (i.e. specific solutions have been selected) and the entity has dropped out of scope — not when the entity was set programmatically via the XML editor.
|
|
1035
|
+
- **Entity list cache race condition** — `loadAllEntities` no longer accepts an `advancedFindOnly` parameter. It always fetches all entities from the server (no `$filter=IsValidForAdvancedFind eq true`). The Advanced Find filter is now applied locally in the EntitySelector `memo`. This eliminates a bug where an earlier `advancedFindOnly=true` load could poison the cache and cause subsequent `advancedFindOnly=false` calls to silently return the wrong (filtered) set.
|
|
1036
|
+
- **Privilege check console noise** — `checkPrivilegeByName` errors for privileges that don't exist in the environment (e.g. `prvDeleteGitorganization` for system tables) are now logged as `console.warn` instead of `console.error`. The UI behavior is unchanged.
|
|
1037
|
+
- **CSP exception** — Removed the invalid `*.crm*.dynamics.com` wildcard from `cspExceptions` (browsers reject multi-depth subdomain wildcards). The only `connect-src` exception kept is `https://*.dynamics.com`.
|
|
1038
|
+
|
|
1039
|
+
### 🏗️ Technical
|
|
1040
|
+
|
|
1041
|
+
- `useAccessMode` preloads only the AF-valid entity metadata cache (`allEntityMetadataCache`) on startup — used by publisher-solution and solution-only modes. The all-entities cache is populated lazily the first time "All Entities" scope mode is used.
|
|
1042
|
+
- `loadAllEntities` / `useLazyMetadata.loadEntities` signatures simplified (no `advancedFindOnly` param — always fetches everything).
|
|
1043
|
+
- `vite.config.ts` — `manualChunks` splits Monaco into its own chunk (`monaco-editor-*.js`, ~3.7 MB; `monaco-editor-*.css`, ~145 kB).
|
|
1044
|
+
- TypeScript strict compile: **zero errors** (`tsc -b --noEmit`).
|
|
1045
|
+
|
|
1046
|
+
---
|
|
1047
|
+
|
|
1048
|
+
## [1.1.0] - 2026-03-23
|
|
1049
|
+
|
|
1050
|
+
### ⬆️ Upgraded
|
|
1051
|
+
|
|
1052
|
+
- **`@pptb/types` 1.0.7 → 1.2.0** — now the authoritative source for all PPTB host API types (`window.dataverseAPI`, `window.toolboxAPI`). Added to `tsconfig.app.json` `types` array so global augmentations apply project-wide without per-file triple-slash references.
|
|
1053
|
+
- **`features.minAPI`** set to `"1.2.0"` in `package.json` to enforce the correct PPTB host version at install time.
|
|
1054
|
+
|
|
1055
|
+
### ✨ Added
|
|
1056
|
+
|
|
1057
|
+
#### Native File Save Dialog
|
|
1058
|
+
- Both export paths (local ExcelJS export and Dataverse `ExportToExcel` action) now use `window.toolboxAPI.fileSystem.saveFile` when running inside the PPTB desktop host, presenting a native OS save-file dialog instead of a forced browser blob download.
|
|
1059
|
+
- Graceful fallback to browser blob/anchor download when running in standalone or dev context (i.e. when `window.toolboxAPI` is not available).
|
|
1060
|
+
|
|
1061
|
+
#### Connection Environment Awareness
|
|
1062
|
+
- `PptbContext` now exposes `environment?: "Dev" | "Test" | "UAT" | "Production"` sourced from the active `DataverseConnection`.
|
|
1063
|
+
- Subscribes to `connection:updated`, `connection:created`, and `connection:deleted` PPTB events to keep the field in sync dynamically.
|
|
1064
|
+
|
|
1065
|
+
#### Package Manifest Hardening
|
|
1066
|
+
- Added top-level `"icon"` field pointing to `icons/icon.svg` (replacing deprecated `configurations.iconUrl`).
|
|
1067
|
+
- `"features"` block added: `{ "multiConnection": "none", "minAPI": "1.2.0" }`.
|
|
1068
|
+
- `cspExceptions` migrated from plain-string arrays to the required object format with `domain` + `exceptionReason` fields (+ `optional: true` on style-src).
|
|
1069
|
+
- Added `"validate": "pptb-validate"` npm script for pre-publish manifest validation.
|
|
1070
|
+
- Added `"finalize-package": "npm run build && npm shrinkwrap"` convenience script.
|
|
1071
|
+
|
|
1072
|
+
### ♻️ Refactored
|
|
1073
|
+
|
|
1074
|
+
#### `pptbClient.ts`
|
|
1075
|
+
- Removed the entire hand-written `declare global { interface Window { dataverseAPI?: { ... } } }` block (~60 lines). The `window.dataverseAPI` global is now typed entirely by `@pptb/types`.
|
|
1076
|
+
- Added local `DataverseFetchXmlResponse` interface to capture OData response annotations beyond what `@pptb/types` `FetchXmlResult` declares (`@Microsoft.Dynamics.CRM.totalrecordcount`, `morerecords`, `pagingcookie`, `totalrecordcountlimitexceeded`). These are OData wire annotations returned directly by Dataverse — not fabricated by PPTB — so the cast is safe at runtime.
|
|
1077
|
+
- `downloadBase64File()` changed from `void` to `async Promise<void>`; uses `fileSystem.saveFile` when available.
|
|
1078
|
+
|
|
1079
|
+
#### `usePptbContext.ts`
|
|
1080
|
+
- Removed the entire hand-written `declare global { interface Window { toolboxAPI?: { ... } } }` block. The `window.toolboxAPI` global is now typed entirely by `@pptb/types`.
|
|
1081
|
+
- `PptbContext` interface: removed legacy `organizationId?: string` (not present in `DataverseConnection` v1.2.0 types); replaced with `environment?: "Dev" | "Test" | "UAT" | "Production"`.
|
|
1082
|
+
|
|
1083
|
+
#### `excelExport.ts`
|
|
1084
|
+
- `downloadExcelFile()` changed from `void` to `async Promise<void>`; uses `fileSystem.saveFile` when available.
|
|
1085
|
+
|
|
1086
|
+
#### `AppShell.tsx`
|
|
1087
|
+
- Both download call sites updated with `await` to match the new async signatures.
|
|
1088
|
+
|
|
1089
|
+
### 🔧 Technical
|
|
1090
|
+
|
|
1091
|
+
- TypeScript strict compile: **zero errors** (`tsc -b --noEmit`).
|
|
1092
|
+
- `pptb-validate`: **✔ Validation passed**.
|
|
1093
|
+
|
|
1094
|
+
---
|
|
1095
|
+
|
|
1096
|
+
## [1.0.6] - 2026-01-14
|
|
1097
|
+
|
|
1098
|
+
### ✨ Added
|
|
1099
|
+
|
|
1100
|
+
#### Select Attributes Dialog (#12)
|
|
1101
|
+
- **Bulk Attribute Selection**: New "Select Attributes" modal dialog for efficient multi-attribute selection
|
|
1102
|
+
- Accessible from entity and link-entity context menus in the tree view
|
|
1103
|
+
- Works on root entity, link-entities, and nested link-entities at any level
|
|
1104
|
+
- **Smart Selection Grid**: DataGrid showing all available entity attributes
|
|
1105
|
+
- Three sortable columns: Logical Name, Display Name, and Data Type
|
|
1106
|
+
- Pre-selects currently selected attributes with checkboxes
|
|
1107
|
+
- Multi-select support for bulk add/remove operations
|
|
1108
|
+
- **Search & Filter**: Integrated SearchBox for quick attribute filtering
|
|
1109
|
+
- Filters across all columns (logical name, display name, data type)
|
|
1110
|
+
- Real-time filtering as you type
|
|
1111
|
+
- **Fixed Headers**: Column headers remain visible while scrolling through attributes
|
|
1112
|
+
- **Confirmation Required**: Changes only applied when user clicks Apply button
|
|
1113
|
+
- Cancel button discards changes without affecting the tree
|
|
1114
|
+
- **Smart Updates**: Intelligently adds new attributes and removes deselected ones
|
|
1115
|
+
- Preserves existing attributes that remain selected
|
|
1116
|
+
- Maintains attribute order and properties
|
|
1117
|
+
- **Performance**: Leverages metadata cache for fast attribute loading
|
|
1118
|
+
- Falls back to API loading if attributes not cached
|
|
1119
|
+
- Shows only attributes valid for Advanced Find
|
|
1120
|
+
|
|
1121
|
+
### 🔧 Technical
|
|
1122
|
+
|
|
1123
|
+
- Added `SelectAttributesDialog` component with FluentUI DataGrid
|
|
1124
|
+
- New `UPDATE_ATTRIBUTES` action in builderStore for bulk attribute operations
|
|
1125
|
+
- Enhanced TreeView with dialog state management and attribute loading
|
|
1126
|
+
- Integrated with existing metadata caching system
|
|
1127
|
+
|
|
1128
|
+
## [1.0.0] - 2024-12-15
|
|
1129
|
+
|
|
1130
|
+
### 🎉 Initial Release
|
|
1131
|
+
|
|
1132
|
+
FetchXML Studio v1.0.0 - A powerful FetchXML query builder for Power Platform ToolBox.
|
|
1133
|
+
|
|
1134
|
+
### ✨ Features
|
|
1135
|
+
|
|
1136
|
+
#### Query Builder
|
|
1137
|
+
- Tree-based visual query builder with hierarchical FetchXML structure
|
|
1138
|
+
- Full entity, attribute, filter, order, and link-entity support
|
|
1139
|
+
- Context-aware properties panel for each node type
|
|
1140
|
+
- Nested filter groups with AND/OR logic
|
|
1141
|
+
- Support for all FetchXML operators (60+ operators)
|
|
1142
|
+
- Aggregate queries with groupby and distinct
|
|
1143
|
+
- Query hints for performance optimization
|
|
1144
|
+
|
|
1145
|
+
#### Relationships & Joins
|
|
1146
|
+
- Relationship picker with 1:N, N:1, N:N categorization
|
|
1147
|
+
- Inner and outer join support
|
|
1148
|
+
- Filter link-entity for any/all/not any/not all scenarios
|
|
1149
|
+
- Value-of conditions for cross-entity comparisons
|
|
1150
|
+
- Entity name conditions for outer join filtering
|
|
1151
|
+
|
|
1152
|
+
#### Results Grid
|
|
1153
|
+
- Virtualized DataGrid for large datasets (react-window)
|
|
1154
|
+
- Rich cell rendering for lookups, option sets, dates, currency
|
|
1155
|
+
- Multi-column sorting (click + Shift+click)
|
|
1156
|
+
- Row selection (single and multi-select)
|
|
1157
|
+
- Resizable and reorderable columns
|
|
1158
|
+
- Formatted values with raw value toggle
|
|
1159
|
+
|
|
1160
|
+
#### FetchXML Editor
|
|
1161
|
+
- Monaco editor with XML syntax highlighting
|
|
1162
|
+
- Bi-directional editing (visual ↔ XML)
|
|
1163
|
+
- Parse FetchXML to visual builder
|
|
1164
|
+
- Copy to clipboard
|
|
1165
|
+
- LayoutXML preview tab
|
|
1166
|
+
|
|
1167
|
+
#### Views
|
|
1168
|
+
- Load system and personal views
|
|
1169
|
+
- Optimized view execution via SavedQuery/UserQuery APIs
|
|
1170
|
+
- Save queries as personal views
|
|
1171
|
+
- Update existing views
|
|
1172
|
+
- Solution-aware save (add to solution)
|
|
1173
|
+
|
|
1174
|
+
#### Data Operations
|
|
1175
|
+
- Export to Excel with native data types
|
|
1176
|
+
- Delete selected records
|
|
1177
|
+
- Bulk delete jobs (async)
|
|
1178
|
+
- Run on-demand workflows on selection
|
|
1179
|
+
- Batch progress tracking with ETA
|
|
1180
|
+
|
|
1181
|
+
#### User Experience
|
|
1182
|
+
- Dark/Light theme support (follows PPTB theme)
|
|
1183
|
+
- Lazy metadata loading
|
|
1184
|
+
- Intelligent caching (per-session)
|
|
1185
|
+
- Resizable split panes
|
|
1186
|
+
- Keyboard shortcuts (Ctrl+Enter to execute)
|
|
1187
|
+
|
|
1188
|
+
#### Security
|
|
1189
|
+
- Privilege-aware operations
|
|
1190
|
+
- Export/Delete privilege checks
|
|
1191
|
+
- Bulk delete privilege validation
|
|
1192
|
+
- Workflow execution privilege checks
|
|
1193
|
+
|
|
1194
|
+
### 🏗️ Technical
|
|
1195
|
+
|
|
1196
|
+
- Built with React 19 and TypeScript 5.9
|
|
1197
|
+
- Fluent UI v9 components
|
|
1198
|
+
- Monaco Editor integration
|
|
1199
|
+
- ExcelJS for native Excel export
|
|
1200
|
+
- Vite build tooling
|
|
1201
|
+
- Power Platform ToolBox integration via @pptb/types
|
|
1202
|
+
|
|
1203
|
+
### 📚 Documentation
|
|
1204
|
+
|
|
1205
|
+
- Comprehensive README with feature overview
|
|
1206
|
+
- Debug logging system documentation
|
|
1207
|
+
- Project structure guide
|
|
1208
|
+
- Contributing guidelines
|
|
1209
|
+
|
|
1210
|
+
---
|
|
1211
|
+
|
|
1212
|
+
## Development History
|
|
1213
|
+
|
|
1214
|
+
### Pre-release commits (dev branch)
|
|
1215
|
+
|
|
1216
|
+
- `feat: Beta release enhancements - Settings, Export, and UX improvements`
|
|
1217
|
+
- `feat(preview): add LayoutXML tab to preview pane`
|
|
1218
|
+
- `feat(commands): add record action commands - delete, bulk delete, workflow`
|
|
1219
|
+
- `feat(columns): Add/Edit Columns panels with related entity support`
|
|
1220
|
+
- `feat(export): add Export to Excel with status feedback and privilege check`
|
|
1221
|
+
- `feat(paging): fix paging with cookies and page size support`
|
|
1222
|
+
- `feat(grid): multi-entity metadata & column display name fixes`
|
|
1223
|
+
- `feat(builder): Sprint 1 - Foundation & Layout improvements`
|
|
1224
|
+
- `feat(save): implement Save View functionality`
|
|
1225
|
+
- `feat(sort): Add multi-column sort with FetchXML integration`
|
|
1226
|
+
- `feat(results): Add Column from Results Grid`
|
|
1227
|
+
- `feat(views): Load view LayoutXML for column configuration`
|
|
1228
|
+
- `feat(layout): Add column resize and reorder`
|
|
1229
|
+
- `feat(layout): Add LayoutXML foundation`
|
|
1230
|
+
- `feat(filter): Add filter link-entity support for any/not any/all scenarios`
|
|
1231
|
+
- `feat(condition): Add valueof cross-entity comparison`
|
|
1232
|
+
- `feat(condition): Add valueof same-row column comparison`
|
|
1233
|
+
- `feat(condition): Add entityname support for outer join filtering`
|
|
1234
|
+
- `feat(views): Add Load View picker and optimized view execution`
|
|
1235
|
+
- `feat(parser): Add FetchXML parser with Monaco editor dialog`
|
|
1236
|
+
- `feat: Implement Phases 1-4 - DataGrid UI polish`
|
|
1237
|
+
- `feat: add filter consistency, cascading validation, and global metadata caching`
|
|
1238
|
+
- `feat(entity-selector): implement advanced filtering with publisher/solution multiselect`
|
|
1239
|
+
- `feat(ui): add resizable panes with visual grip indicators and command bar`
|
|
1240
|
+
- `Initial commit - setup main branch`
|