@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +1 -1
  3. package/dist/assets/index-DD9fzkbz.js +232 -0
  4. package/dist/index.html +70 -0
  5. package/index.html +0 -1
  6. package/npm-shrinkwrap.json +6329 -0
  7. package/package.json +95 -51
  8. package/CHANGELOG.md +0 -120
  9. package/eslint.config.js +0 -23
  10. package/public/vite.svg +0 -1
  11. package/src/app/AppShell.tsx +0 -1733
  12. package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
  13. package/src/assets/react.svg +0 -1
  14. package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
  15. package/src/features/fetchxml/api/excelExport.ts +0 -318
  16. package/src/features/fetchxml/api/formattedValues.ts +0 -107
  17. package/src/features/fetchxml/api/pptbClient.ts +0 -2635
  18. package/src/features/fetchxml/model/displaySettings.ts +0 -18
  19. package/src/features/fetchxml/model/fetchxml.ts +0 -346
  20. package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
  21. package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
  22. package/src/features/fetchxml/model/layoutxml.ts +0 -372
  23. package/src/features/fetchxml/model/nodes.ts +0 -255
  24. package/src/features/fetchxml/model/operators.ts +0 -624
  25. package/src/features/fetchxml/model/treeUtils.ts +0 -174
  26. package/src/features/fetchxml/state/builderStore.tsx +0 -1160
  27. package/src/features/fetchxml/state/cache.ts +0 -393
  28. package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
  29. package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
  30. package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
  31. package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
  32. package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
  33. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
  34. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
  35. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
  36. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
  37. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
  38. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
  39. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
  40. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
  41. package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
  42. package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
  43. package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
  44. package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
  45. package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
  46. package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
  47. package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
  48. package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
  49. package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
  50. package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
  51. package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
  52. package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
  53. package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
  54. package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
  55. package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
  56. package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
  57. package/src/main.tsx +0 -11
  58. package/src/shared/components/AttributePicker.tsx +0 -138
  59. package/src/shared/components/BetweenInputPicker.tsx +0 -120
  60. package/src/shared/components/BooleanValuePicker.tsx +0 -165
  61. package/src/shared/components/DateTimeInputPicker.tsx +0 -170
  62. package/src/shared/components/DynamicValueList.tsx +0 -128
  63. package/src/shared/components/ExcelIcon.tsx +0 -30
  64. package/src/shared/components/MultiValuePicker.tsx +0 -178
  65. package/src/shared/components/NumericInputPicker.tsx +0 -156
  66. package/src/shared/components/OptionSetValuePicker.tsx +0 -150
  67. package/src/shared/components/RelationshipPicker.tsx +0 -268
  68. package/src/shared/contexts/ThemeContext.tsx +0 -19
  69. package/src/shared/hooks/useAccessMode.ts +0 -77
  70. package/src/shared/hooks/useLazyMetadata.ts +0 -132
  71. package/src/shared/hooks/usePptbContext.ts +0 -170
  72. package/src/shared/hooks/usePublisherFilter.ts +0 -214
  73. package/src/shared/hooks/useSolutionFilter.ts +0 -162
  74. package/src/shared/utils/debug.ts +0 -87
  75. package/tsconfig.app.json +0 -28
  76. package/tsconfig.json +0 -7
  77. package/tsconfig.node.json +0 -26
  78. package/vite.config.ts +0 -7
@@ -1,114 +0,0 @@
1
- /**
2
- * SolutionPicker - Dropdown for selecting an unmanaged solution
3
- * Used when saving system views to allow adding them to a specific solution
4
- */
5
-
6
- import { useMemo } from "react";
7
- import { Combobox, Option, Field, makeStyles, tokens, Text } from "@fluentui/react-components";
8
- import type { Solution } from "../../api/pptbClient";
9
-
10
- const useStyles = makeStyles({
11
- container: {
12
- display: "flex",
13
- flexDirection: "column",
14
- gap: "4px",
15
- },
16
- combobox: {
17
- width: "100%",
18
- },
19
- hint: {
20
- fontSize: "12px",
21
- color: tokens.colorNeutralForeground3,
22
- },
23
- solutionOption: {
24
- display: "flex",
25
- flexDirection: "column",
26
- gap: "2px",
27
- },
28
- solutionName: {
29
- fontWeight: 500,
30
- },
31
- solutionUniqueName: {
32
- fontSize: "11px",
33
- color: tokens.colorNeutralForeground3,
34
- },
35
- });
36
-
37
- interface SolutionPickerProps {
38
- /** Available unmanaged solutions */
39
- solutions: Solution[];
40
- /** Currently selected solution */
41
- selectedSolution: Solution | null;
42
- /** Selection change callback */
43
- onSolutionChange: (solution: Solution | null) => void;
44
- /** Disabled state */
45
- disabled?: boolean;
46
- }
47
-
48
- export function SolutionPicker({
49
- solutions,
50
- selectedSolution,
51
- onSolutionChange,
52
- disabled = false,
53
- }: SolutionPickerProps) {
54
- const styles = useStyles();
55
-
56
- // Sort solutions by friendly name
57
- const sortedSolutions = useMemo(() => {
58
- return [...solutions].sort((a, b) =>
59
- (a.friendlyname || a.uniquename).localeCompare(b.friendlyname || b.uniquename)
60
- );
61
- }, [solutions]);
62
-
63
- // Handle selection change
64
- const handleOptionSelect = (_: unknown, data: { optionValue?: string; optionText?: string }) => {
65
- if (data.optionValue === "__none__") {
66
- onSolutionChange(null);
67
- } else {
68
- const solution = solutions.find((s) => s.solutionid === data.optionValue);
69
- onSolutionChange(solution || null);
70
- }
71
- };
72
-
73
- return (
74
- <div className={styles.container}>
75
- <Field label="Add to Solution" hint="Select a solution to include the view in">
76
- <Combobox
77
- className={styles.combobox}
78
- placeholder="Select solution (optional)"
79
- value={selectedSolution?.friendlyname || ""}
80
- onOptionSelect={handleOptionSelect}
81
- disabled={disabled}
82
- >
83
- <Option value="__none__" text="">
84
- <span style={{ fontStyle: "italic", color: tokens.colorNeutralForeground3 }}>
85
- None - don't add to any solution
86
- </span>
87
- </Option>
88
- {sortedSolutions.map((solution) => (
89
- <Option
90
- key={solution.solutionid}
91
- value={solution.solutionid}
92
- text={solution.friendlyname || solution.uniquename}
93
- >
94
- <div className={styles.solutionOption}>
95
- <span className={styles.solutionName}>
96
- {solution.friendlyname || solution.uniquename}
97
- </span>
98
- <span className={styles.solutionUniqueName}>
99
- {solution.uniquename}
100
- {solution.version && ` (v${solution.version})`}
101
- </span>
102
- </div>
103
- </Option>
104
- ))}
105
- </Combobox>
106
- </Field>
107
- {solutions.length === 0 && (
108
- <Text className={styles.hint}>
109
- No unmanaged solutions available. The view will be created in the Default solution.
110
- </Text>
111
- )}
112
- </div>
113
- );
114
- }
@@ -1,437 +0,0 @@
1
- /**
2
- * Workflow Picker Dialog
3
- * Allows user to select and run an on-demand workflow on selected records
4
- */
5
-
6
- import { useCallback, useState, useEffect } from "react";
7
- import {
8
- Dialog,
9
- DialogSurface,
10
- DialogTitle,
11
- DialogBody,
12
- DialogContent,
13
- DialogActions,
14
- Button,
15
- Text,
16
- Spinner,
17
- Radio,
18
- RadioGroup,
19
- ProgressBar,
20
- makeStyles,
21
- tokens,
22
- MessageBar,
23
- MessageBarBody,
24
- MessageBarTitle,
25
- } from "@fluentui/react-components";
26
- import { Play20Regular, CheckmarkCircle20Regular, Warning20Regular } from "@fluentui/react-icons";
27
- import type { WorkflowInfo, WorkflowBatchProgress } from "../../api/pptbClient";
28
-
29
- const useStyles = makeStyles({
30
- content: {
31
- display: "flex",
32
- flexDirection: "column",
33
- gap: tokens.spacingVerticalM,
34
- },
35
- workflowList: {
36
- maxHeight: "300px",
37
- overflowY: "auto",
38
- border: `1px solid ${tokens.colorNeutralStroke1}`,
39
- borderRadius: tokens.borderRadiusMedium,
40
- padding: tokens.spacingVerticalS,
41
- },
42
- workflowOption: {
43
- display: "flex",
44
- flexDirection: "column",
45
- padding: tokens.spacingVerticalXS,
46
- },
47
- workflowDescription: {
48
- marginLeft: "28px", // Align with radio label
49
- color: tokens.colorNeutralForeground3,
50
- fontSize: tokens.fontSizeBase200,
51
- },
52
- progressSection: {
53
- display: "flex",
54
- flexDirection: "column",
55
- gap: tokens.spacingVerticalS,
56
- },
57
- progressStats: {
58
- display: "flex",
59
- justifyContent: "space-between",
60
- alignItems: "center",
61
- },
62
- eta: {
63
- color: tokens.colorNeutralForeground3,
64
- fontSize: tokens.fontSizeBase200,
65
- },
66
- resultSection: {
67
- display: "flex",
68
- flexDirection: "column",
69
- gap: tokens.spacingVerticalS,
70
- },
71
- successRow: {
72
- display: "flex",
73
- alignItems: "center",
74
- gap: tokens.spacingHorizontalS,
75
- color: tokens.colorPaletteGreenForeground1,
76
- },
77
- errorRow: {
78
- display: "flex",
79
- alignItems: "center",
80
- gap: tokens.spacingHorizontalS,
81
- color: tokens.colorPaletteRedForeground1,
82
- },
83
- emptyState: {
84
- display: "flex",
85
- flexDirection: "column",
86
- alignItems: "center",
87
- justifyContent: "center",
88
- padding: tokens.spacingVerticalXXL,
89
- color: tokens.colorNeutralForeground3,
90
- },
91
- loadingState: {
92
- display: "flex",
93
- alignItems: "center",
94
- justifyContent: "center",
95
- padding: tokens.spacingVerticalXL,
96
- gap: tokens.spacingHorizontalS,
97
- },
98
- });
99
-
100
- export interface WorkflowPickerDialogProps {
101
- /** Whether the dialog is open */
102
- open: boolean;
103
- /** Number of records selected */
104
- recordCount: number;
105
- /** Entity display name */
106
- entityDisplayName?: string;
107
- /** Pre-selected workflow (from menu selection) */
108
- preSelectedWorkflow?: WorkflowInfo;
109
- /** Called when dialog is closed */
110
- onClose: () => void;
111
- /** Called to fetch available workflows */
112
- onFetchWorkflows: () => Promise<WorkflowInfo[]>;
113
- /** Called when workflow execution is confirmed */
114
- onExecute: (
115
- workflow: WorkflowInfo,
116
- onProgress: (progress: WorkflowBatchProgress) => void
117
- ) => Promise<{ succeeded: number; failed: number; errors: string[] }>;
118
- }
119
-
120
- type DialogState = "loading" | "select" | "executing" | "complete" | "error";
121
-
122
- export function WorkflowPickerDialog({
123
- open,
124
- recordCount,
125
- entityDisplayName,
126
- preSelectedWorkflow,
127
- onClose,
128
- onFetchWorkflows,
129
- onExecute,
130
- }: WorkflowPickerDialogProps) {
131
- const styles = useStyles();
132
- const [state, setState] = useState<DialogState>("loading");
133
- const [workflows, setWorkflows] = useState<WorkflowInfo[]>([]);
134
- const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | null>(null);
135
- const [error, setError] = useState<string | null>(null);
136
- const [progress, setProgress] = useState<WorkflowBatchProgress | null>(null);
137
- const [result, setResult] = useState<{
138
- succeeded: number;
139
- failed: number;
140
- errors: string[];
141
- } | null>(null);
142
-
143
- // Load workflows when dialog opens
144
- useEffect(() => {
145
- if (open) {
146
- setState("loading");
147
- setSelectedWorkflowId(null);
148
- setError(null);
149
- setResult(null);
150
- setProgress(null);
151
-
152
- onFetchWorkflows()
153
- .then((wfs) => {
154
- setWorkflows(wfs);
155
- setState("select");
156
- // Auto-select: preSelectedWorkflow takes priority, then single workflow
157
- if (preSelectedWorkflow) {
158
- // Verify the pre-selected workflow exists in the fetched list
159
- const found = wfs.find((w) => w.workflowid === preSelectedWorkflow.workflowid);
160
- if (found) {
161
- setSelectedWorkflowId(found.workflowid);
162
- }
163
- } else if (wfs.length === 1) {
164
- setSelectedWorkflowId(wfs[0].workflowid);
165
- }
166
- })
167
- .catch((err) => {
168
- setError(err instanceof Error ? err.message : "Failed to load workflows");
169
- setState("error");
170
- });
171
- }
172
- }, [open, onFetchWorkflows, preSelectedWorkflow]);
173
-
174
- const handleExecute = useCallback(async () => {
175
- const workflow = workflows.find((w) => w.workflowid === selectedWorkflowId);
176
- if (!workflow) return;
177
-
178
- setState("executing");
179
- setProgress(null);
180
- setError(null);
181
-
182
- try {
183
- const res = await onExecute(workflow, (progressUpdate) => {
184
- // Progress now comes from executeWorkflowBatch with batch info and ETA
185
- setProgress(progressUpdate);
186
- });
187
- setResult(res);
188
- setState("complete");
189
- } catch (err) {
190
- setError(err instanceof Error ? err.message : "Failed to execute workflow");
191
- setState("error");
192
- }
193
- }, [workflows, selectedWorkflowId, onExecute]);
194
-
195
- const handleClose = useCallback(() => {
196
- if (state !== "executing") {
197
- onClose();
198
- }
199
- }, [state, onClose]);
200
-
201
- const selectedWorkflow = workflows.find((w) => w.workflowid === selectedWorkflowId);
202
-
203
- /**
204
- * Intelligently format ETA based on duration:
205
- * - < 1 minute: show seconds
206
- * - < 1 hour: show minutes and seconds
207
- * - < 12 hours: show hours and minutes
208
- * - >= 12 hours: show estimated completion date/time
209
- */
210
- const formatEta = (seconds: number): string => {
211
- if (seconds < 60) {
212
- return `~${seconds}s remaining`;
213
- }
214
-
215
- const minutes = Math.floor(seconds / 60);
216
- const hours = Math.floor(minutes / 60);
217
-
218
- if (minutes < 60) {
219
- const secs = seconds % 60;
220
- return `~${minutes}m ${secs}s remaining`;
221
- }
222
-
223
- if (hours < 12) {
224
- const mins = minutes % 60;
225
- return `~${hours}h ${mins}m remaining`;
226
- }
227
-
228
- // >= 12 hours: show estimated completion date/time
229
- const completionDate = new Date(Date.now() + seconds * 1000);
230
- const isToday = completionDate.toDateString() === new Date().toDateString();
231
- const isTomorrow =
232
- completionDate.toDateString() === new Date(Date.now() + 86400000).toDateString();
233
-
234
- const timeStr = completionDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
235
-
236
- if (isToday) {
237
- return `Est. completion: today at ${timeStr}`;
238
- } else if (isTomorrow) {
239
- return `Est. completion: tomorrow at ${timeStr}`;
240
- } else {
241
- const dateStr = completionDate.toLocaleDateString([], { month: "short", day: "numeric" });
242
- return `Est. completion: ${dateStr} at ${timeStr}`;
243
- }
244
- };
245
-
246
- const renderContent = () => {
247
- switch (state) {
248
- case "loading":
249
- return (
250
- <div className={styles.loadingState}>
251
- <Spinner size="small" />
252
- <Text>Loading available workflows...</Text>
253
- </div>
254
- );
255
-
256
- case "select":
257
- if (workflows.length === 0) {
258
- return (
259
- <div className={styles.emptyState}>
260
- <Text weight="semibold">No workflows available</Text>
261
- <Text size={200}>
262
- There are no on-demand workflows configured for {entityDisplayName || "this entity"}
263
- .
264
- </Text>
265
- </div>
266
- );
267
- }
268
-
269
- return (
270
- <>
271
- <Text>
272
- Select a workflow to run on {recordCount} {recordCount === 1 ? "record" : "records"}:
273
- </Text>
274
-
275
- <div className={styles.workflowList}>
276
- <RadioGroup
277
- value={selectedWorkflowId || ""}
278
- onChange={(_, data) => setSelectedWorkflowId(data.value)}
279
- >
280
- {workflows.map((wf) => (
281
- <div key={wf.workflowid} className={styles.workflowOption}>
282
- <Radio value={wf.workflowid} label={wf.name} />
283
- {wf.description && (
284
- <Text className={styles.workflowDescription}>{wf.description}</Text>
285
- )}
286
- </div>
287
- ))}
288
- </RadioGroup>
289
- </div>
290
- </>
291
- );
292
-
293
- case "executing":
294
- return (
295
- <div className={styles.progressSection}>
296
- <MessageBar intent="info">
297
- <MessageBarBody>
298
- <MessageBarTitle>Running workflow...</MessageBarTitle>
299
- Executing <strong>{selectedWorkflow?.name}</strong> on {recordCount} records
300
- {progress && ` (batch ${progress.batchesCompleted} of ${progress.totalBatches})`}
301
- </MessageBarBody>
302
- </MessageBar>
303
- {progress ? (
304
- <>
305
- <ProgressBar value={progress.completed} max={progress.total} thickness="large" />
306
- <div className={styles.progressStats}>
307
- <Text size={200}>
308
- {progress.completed} of {progress.total} completed ({progress.succeeded}{" "}
309
- succeeded, {progress.failed} failed)
310
- </Text>
311
- {progress.estimatedSecondsRemaining !== undefined &&
312
- progress.estimatedSecondsRemaining > 0 && (
313
- <Text className={styles.eta}>
314
- {formatEta(progress.estimatedSecondsRemaining)}
315
- </Text>
316
- )}
317
- </div>
318
- </>
319
- ) : (
320
- <div
321
- style={{ display: "flex", alignItems: "center", gap: tokens.spacingHorizontalS }}
322
- >
323
- <Spinner size="small" />
324
- <Text>Starting workflow execution...</Text>
325
- </div>
326
- )}
327
- </div>
328
- );
329
-
330
- case "complete":
331
- return (
332
- <div className={styles.resultSection}>
333
- {result && result.succeeded > 0 && (
334
- <div className={styles.successRow}>
335
- <CheckmarkCircle20Regular />
336
- <Text weight="semibold">
337
- {result.succeeded} record{result.succeeded !== 1 ? "s" : ""} processed
338
- successfully
339
- </Text>
340
- </div>
341
- )}
342
-
343
- {result && result.failed > 0 && (
344
- <>
345
- <div className={styles.errorRow}>
346
- <Warning20Regular />
347
- <Text weight="semibold">
348
- {result.failed} record{result.failed !== 1 ? "s" : ""} failed
349
- </Text>
350
- </div>
351
- {result.errors.length > 0 && (
352
- <MessageBar intent="error">
353
- <MessageBarBody>
354
- <MessageBarTitle>Errors:</MessageBarTitle>
355
- {result.errors.slice(0, 5).map((err, i) => (
356
- <Text key={i} size={200} block>
357
- {err}
358
- </Text>
359
- ))}
360
- {result.errors.length > 5 && (
361
- <Text size={200}>...and {result.errors.length - 5} more</Text>
362
- )}
363
- </MessageBarBody>
364
- </MessageBar>
365
- )}
366
- </>
367
- )}
368
- </div>
369
- );
370
-
371
- case "error":
372
- return (
373
- <MessageBar intent="error">
374
- <MessageBarBody>
375
- <MessageBarTitle>Error</MessageBarTitle>
376
- {error}
377
- </MessageBarBody>
378
- </MessageBar>
379
- );
380
- }
381
- };
382
-
383
- const renderActions = () => {
384
- switch (state) {
385
- case "loading":
386
- return (
387
- <Button appearance="secondary" onClick={handleClose}>
388
- Cancel
389
- </Button>
390
- );
391
-
392
- case "select":
393
- return (
394
- <>
395
- <Button appearance="secondary" onClick={handleClose}>
396
- Cancel
397
- </Button>
398
- <Button
399
- appearance="primary"
400
- icon={<Play20Regular />}
401
- onClick={handleExecute}
402
- disabled={!selectedWorkflowId || workflows.length === 0}
403
- >
404
- Run Workflow
405
- </Button>
406
- </>
407
- );
408
-
409
- case "executing":
410
- return (
411
- <Button appearance="secondary" disabled>
412
- Running...
413
- </Button>
414
- );
415
-
416
- case "complete":
417
- case "error":
418
- return (
419
- <Button appearance="primary" onClick={handleClose}>
420
- Close
421
- </Button>
422
- );
423
- }
424
- };
425
-
426
- return (
427
- <Dialog open={open} onOpenChange={(_, data) => !data.open && handleClose()}>
428
- <DialogSurface>
429
- <DialogTitle>Run Workflow</DialogTitle>
430
- <DialogBody>
431
- <DialogContent className={styles.content}>{renderContent()}</DialogContent>
432
- </DialogBody>
433
- <DialogActions>{renderActions()}</DialogActions>
434
- </DialogSurface>
435
- </Dialog>
436
- );
437
- }