@mohsinonxrm/pptb-fetchxml-studio 1.0.0

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 (74) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +250 -0
  3. package/eslint.config.js +23 -0
  4. package/index.html +71 -0
  5. package/package.json +53 -0
  6. package/public/vite.svg +1 -0
  7. package/src/app/AppShell.tsx +1733 -0
  8. package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
  9. package/src/assets/react.svg +1 -0
  10. package/src/features/fetchxml/api/dataverseMetadata.ts +192 -0
  11. package/src/features/fetchxml/api/excelExport.ts +318 -0
  12. package/src/features/fetchxml/api/formattedValues.ts +107 -0
  13. package/src/features/fetchxml/api/pptbClient.ts +2635 -0
  14. package/src/features/fetchxml/model/displaySettings.ts +18 -0
  15. package/src/features/fetchxml/model/fetchxml.ts +346 -0
  16. package/src/features/fetchxml/model/fetchxmlIntellisense.ts +583 -0
  17. package/src/features/fetchxml/model/fetchxmlParser.ts +708 -0
  18. package/src/features/fetchxml/model/layoutxml.ts +372 -0
  19. package/src/features/fetchxml/model/nodes.ts +255 -0
  20. package/src/features/fetchxml/model/operators.ts +624 -0
  21. package/src/features/fetchxml/model/treeUtils.ts +174 -0
  22. package/src/features/fetchxml/state/builderStore.tsx +1160 -0
  23. package/src/features/fetchxml/state/cache.ts +393 -0
  24. package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +352 -0
  25. package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +396 -0
  26. package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +612 -0
  27. package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +114 -0
  28. package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +437 -0
  29. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +214 -0
  30. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +533 -0
  31. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +296 -0
  32. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +106 -0
  33. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +433 -0
  34. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +161 -0
  35. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +194 -0
  36. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +225 -0
  37. package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +604 -0
  38. package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +800 -0
  39. package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +170 -0
  40. package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +356 -0
  41. package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +372 -0
  42. package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +399 -0
  43. package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +189 -0
  44. package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +171 -0
  45. package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +471 -0
  46. package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +430 -0
  47. package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +983 -0
  48. package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +65 -0
  49. package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +175 -0
  50. package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +788 -0
  51. package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +274 -0
  52. package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +174 -0
  53. package/src/main.tsx +11 -0
  54. package/src/shared/components/AttributePicker.tsx +138 -0
  55. package/src/shared/components/BetweenInputPicker.tsx +120 -0
  56. package/src/shared/components/BooleanValuePicker.tsx +165 -0
  57. package/src/shared/components/DateTimeInputPicker.tsx +170 -0
  58. package/src/shared/components/DynamicValueList.tsx +128 -0
  59. package/src/shared/components/ExcelIcon.tsx +30 -0
  60. package/src/shared/components/MultiValuePicker.tsx +178 -0
  61. package/src/shared/components/NumericInputPicker.tsx +156 -0
  62. package/src/shared/components/OptionSetValuePicker.tsx +150 -0
  63. package/src/shared/components/RelationshipPicker.tsx +268 -0
  64. package/src/shared/contexts/ThemeContext.tsx +19 -0
  65. package/src/shared/hooks/useAccessMode.ts +77 -0
  66. package/src/shared/hooks/useLazyMetadata.ts +132 -0
  67. package/src/shared/hooks/usePptbContext.ts +170 -0
  68. package/src/shared/hooks/usePublisherFilter.ts +214 -0
  69. package/src/shared/hooks/useSolutionFilter.ts +162 -0
  70. package/src/shared/utils/debug.ts +87 -0
  71. package/tsconfig.app.json +28 -0
  72. package/tsconfig.json +7 -0
  73. package/tsconfig.node.json +26 -0
  74. package/vite.config.ts +7 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Utility functions for traversing and querying FetchXML tree nodes
3
+ */
4
+
5
+ import type { FetchNode, EntityNode, LinkEntityNode, NodeId, FilterNode } from "./nodes";
6
+
7
+ /**
8
+ * Information about a link-entity that can be referenced in entityname attribute
9
+ */
10
+ export interface LinkEntityReference {
11
+ /** The alias if specified, otherwise the entity logical name */
12
+ identifier: string;
13
+ /** The entity logical name */
14
+ entityName: string;
15
+ /** The alias (if specified) */
16
+ alias?: string;
17
+ /** Display label for the UI (e.g., "contact_alias (contact)") */
18
+ displayLabel: string;
19
+ /** The node ID of the link-entity */
20
+ nodeId: NodeId;
21
+ }
22
+
23
+ /**
24
+ * Recursively collects all link-entity references from the FetchXML tree.
25
+ * These can be used in the entityname attribute of conditions.
26
+ *
27
+ * @param node - The FetchNode to traverse
28
+ * @returns Array of LinkEntityReference objects
29
+ */
30
+ export function collectLinkEntityReferences(node: FetchNode | null): LinkEntityReference[] {
31
+ if (!node?.entity) {
32
+ return [];
33
+ }
34
+
35
+ const references: LinkEntityReference[] = [];
36
+
37
+ function collectFromEntity(entity: EntityNode | LinkEntityNode): void {
38
+ // For link-entities, add them as references
39
+ if (entity.type === "link-entity") {
40
+ const linkEntity = entity as LinkEntityNode;
41
+ const identifier = linkEntity.alias || linkEntity.name;
42
+ references.push({
43
+ identifier,
44
+ entityName: linkEntity.name,
45
+ alias: linkEntity.alias,
46
+ displayLabel: linkEntity.alias
47
+ ? `${linkEntity.alias} (${linkEntity.name})`
48
+ : linkEntity.name,
49
+ nodeId: linkEntity.id,
50
+ });
51
+ }
52
+
53
+ // Recursively process nested link-entities
54
+ if (entity.links) {
55
+ for (const link of entity.links) {
56
+ collectFromEntity(link);
57
+ }
58
+ }
59
+ }
60
+
61
+ // Start from root entity's links
62
+ for (const link of node.entity.links) {
63
+ collectFromEntity(link);
64
+ }
65
+
66
+ return references;
67
+ }
68
+
69
+ /**
70
+ * Determines if a condition is within the root entity's filter tree.
71
+ * Conditions in root entity filters can use entityname to reference link-entities.
72
+ *
73
+ * @param fetchNode - The FetchNode to search in
74
+ * @param conditionId - The ID of the condition to find
75
+ * @returns True if the condition is in the root entity's filter tree
76
+ */
77
+ export function isConditionInRootEntityFilter(
78
+ fetchNode: FetchNode | null,
79
+ conditionId: NodeId
80
+ ): boolean {
81
+ if (!fetchNode?.entity) {
82
+ return false;
83
+ }
84
+
85
+ // Check if condition exists in root entity's filters
86
+ function checkInFilters(filters: FilterNode[]): boolean {
87
+ for (const filter of filters) {
88
+ // Check conditions in this filter
89
+ for (const condition of filter.conditions) {
90
+ if (condition.id === conditionId) {
91
+ return true;
92
+ }
93
+ }
94
+ // Recursively check subfilters
95
+ if (checkInFilters(filter.subfilters)) {
96
+ return true;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+
102
+ return checkInFilters(fetchNode.entity.filters);
103
+ }
104
+
105
+ /**
106
+ * Finds the parent entity or link-entity for a given node ID.
107
+ *
108
+ * @param fetchNode - The FetchNode to search in
109
+ * @param nodeId - The ID of the node to find the parent for
110
+ * @returns The entity/link-entity name or null if not found
111
+ */
112
+ export function findParentEntityForNode(
113
+ fetchNode: FetchNode | null,
114
+ nodeId: NodeId
115
+ ): string | null {
116
+ if (!fetchNode?.entity) {
117
+ return null;
118
+ }
119
+
120
+ function findInEntity(
121
+ entity: EntityNode | LinkEntityNode,
122
+ targetId: NodeId,
123
+ parentName: string
124
+ ): string | null {
125
+ // Check in attributes
126
+ for (const attr of entity.attributes) {
127
+ if (attr.id === targetId) return parentName;
128
+ }
129
+
130
+ // Check in orders
131
+ if ("orders" in entity) {
132
+ for (const order of entity.orders) {
133
+ if (order.id === targetId) return parentName;
134
+ }
135
+ }
136
+
137
+ // Check in filters and conditions
138
+ for (const filter of entity.filters) {
139
+ const result = findInFilter(filter, targetId, parentName);
140
+ if (result) return result;
141
+ }
142
+
143
+ // Check in nested link-entities
144
+ const currentName = entity.type === "link-entity" ? entity.alias || entity.name : entity.name;
145
+ for (const link of entity.links) {
146
+ if (link.id === targetId) return parentName;
147
+ const result = findInEntity(link, targetId, currentName);
148
+ if (result) return result;
149
+ }
150
+
151
+ return null;
152
+ }
153
+
154
+ function findInFilter(filter: FilterNode, targetId: NodeId, parentName: string): string | null {
155
+ if (filter.id === targetId) return parentName;
156
+
157
+ for (const condition of filter.conditions) {
158
+ if (condition.id === targetId) return parentName;
159
+ }
160
+
161
+ for (const subfilter of filter.subfilters) {
162
+ const result = findInFilter(subfilter, targetId, parentName);
163
+ if (result) return result;
164
+ }
165
+
166
+ return null;
167
+ }
168
+
169
+ // Check if it's the fetch or entity node itself
170
+ if (fetchNode.id === nodeId) return null;
171
+ if (fetchNode.entity.id === nodeId) return fetchNode.entity.name;
172
+
173
+ return findInEntity(fetchNode.entity, nodeId, fetchNode.entity.name);
174
+ }