pptb-standard-sample-tool 1.2.7 → 1.3.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.
package/dist/app.js CHANGED
@@ -26,6 +26,23 @@ let securitySuites = null;
26
26
  let terminalFeature = null;
27
27
  let fileSystemFeature = null;
28
28
  let inputEntityName = null;
29
+ const powerPlatformNamespaces = [
30
+ "Analytics",
31
+ "AppManagement",
32
+ "Authorization",
33
+ "Connectivity",
34
+ "CopilotStudio",
35
+ "Dynamics",
36
+ "EnvironmentManagement",
37
+ "Governance",
38
+ "Licensing",
39
+ "PowerApps",
40
+ "PowerAutomate",
41
+ "PowerPages",
42
+ "ResourceQuery",
43
+ "UserManagement",
44
+ "WorkflowAgents",
45
+ ];
29
46
  /**
30
47
  * Initialize the application
31
48
  */
@@ -272,6 +289,7 @@ function setupEventHandlers() {
272
289
  document.getElementById("query-role-definitions-btn")?.addEventListener("click", queryRoleDefinitions);
273
290
  document.getElementById("assign-role-btn")?.addEventListener("click", assignRole);
274
291
  document.getElementById("query-role-assignments-btn")?.addEventListener("click", queryRoleAssignments);
292
+ document.getElementById("pp-generic-execute-btn")?.addEventListener("click", executeGenericPowerPlatformEndpoint);
275
293
  // Clear log button
276
294
  document.getElementById("clear-log-btn")?.addEventListener("click", clearLog);
277
295
  // Advanced utilities demos
@@ -1212,6 +1230,71 @@ async function queryRoleAssignments() {
1212
1230
  await showNotification("Error", `Failed to retrieve role assignments: ${errorMessage}`, "error");
1213
1231
  }
1214
1232
  }
1233
+ async function executeGenericPowerPlatformEndpoint() {
1234
+ const output = document.getElementById("pp-generic-output");
1235
+ const method = document.getElementById("pp-generic-method")?.value;
1236
+ const namespace = document.getElementById("pp-generic-namespace")?.value;
1237
+ const path = document.getElementById("pp-generic-path")?.value?.trim();
1238
+ const rawBody = document.getElementById("pp-generic-body")?.value?.trim();
1239
+ if (!method || !namespace || !path) {
1240
+ await showNotification("Missing Inputs", "Method, namespace, and path are required", "warning");
1241
+ if (output)
1242
+ output.textContent = "Please provide method, namespace, and path.";
1243
+ return;
1244
+ }
1245
+ if (!powerPlatformNamespaces.includes(namespace)) {
1246
+ await showNotification("Invalid Namespace", `Unsupported namespace: ${namespace}`, "error");
1247
+ if (output)
1248
+ output.textContent = `Unsupported namespace: ${namespace}`;
1249
+ return;
1250
+ }
1251
+ let parsedBody = undefined;
1252
+ if (rawBody) {
1253
+ try {
1254
+ parsedBody = JSON.parse(rawBody);
1255
+ }
1256
+ catch {
1257
+ await showNotification("Invalid JSON Body", "Request body must be valid JSON", "error");
1258
+ if (output)
1259
+ output.textContent = "Request body is not valid JSON.";
1260
+ return;
1261
+ }
1262
+ }
1263
+ try {
1264
+ if (output) {
1265
+ output.textContent = `Executing ${method.toUpperCase()} ${namespace}/${path}\n\n`;
1266
+ }
1267
+ const namespaceClient = powerplatform[namespace];
1268
+ if (!namespaceClient) {
1269
+ throw new Error(`Namespace '${namespace}' not found on powerplatformAPI`);
1270
+ }
1271
+ const methodClient = namespaceClient[method];
1272
+ if (typeof methodClient !== "function") {
1273
+ throw new Error(`Method '${method}' is not available on namespace '${namespace}'`);
1274
+ }
1275
+ let result;
1276
+ if (method === "Get") {
1277
+ result = await methodClient(path);
1278
+ }
1279
+ else if (method === "Delete") {
1280
+ result = parsedBody === undefined ? await methodClient(path) : await methodClient(path, undefined, undefined, parsedBody);
1281
+ }
1282
+ else {
1283
+ result = await methodClient(path, parsedBody ?? {});
1284
+ }
1285
+ if (output) {
1286
+ output.textContent += JSON.stringify(result, null, 2);
1287
+ }
1288
+ log(`Executed ${method} on ${namespace}: ${path}`, "success");
1289
+ }
1290
+ catch (error) {
1291
+ const message = error.message;
1292
+ if (output)
1293
+ output.textContent = `Error executing endpoint: ${message}`;
1294
+ log(`Generic Power Platform endpoint error: ${message}`, "error");
1295
+ await showNotification("Endpoint Execution Failed", message, "error");
1296
+ }
1297
+ }
1215
1298
  // Initialize when DOM is ready
1216
1299
  if (document.readyState === "loading") {
1217
1300
  document.addEventListener("DOMContentLoaded", initialize);
package/dist/headless.js CHANGED
@@ -1,5 +1,43 @@
1
1
  "use strict";
2
2
  /// <reference types="@pptb/types" />
3
+ function getDataverseApi() {
4
+ const runtime = globalThis;
5
+ const api = runtime.dataverseAPI ?? runtime.window?.dataverseAPI;
6
+ if (!api) {
7
+ throw new Error("Dataverse API is not available in this runtime context");
8
+ }
9
+ return api;
10
+ }
11
+ function sanitizeEntityName(value) {
12
+ const trimmed = value.trim();
13
+ if (!/^[A-Za-z0-9_]+$/.test(trimmed)) {
14
+ throw new Error("Invalid entity name. Only letters, numbers, and underscores are allowed.");
15
+ }
16
+ return trimmed.toLowerCase();
17
+ }
18
+ async function resolveEntityAttributes(api, entityName) {
19
+ try {
20
+ const metadata = await api.getEntityMetadata(entityName, true, ["PrimaryIdAttribute", "PrimaryNameAttribute"]);
21
+ const idAttribute = typeof metadata.PrimaryIdAttribute === "string" && metadata.PrimaryIdAttribute.trim().length > 0 ? metadata.PrimaryIdAttribute : `${entityName}id`;
22
+ const nameAttribute = typeof metadata.PrimaryNameAttribute === "string" && metadata.PrimaryNameAttribute.trim().length > 0 ? metadata.PrimaryNameAttribute : undefined;
23
+ return { idAttribute, nameAttribute };
24
+ }
25
+ catch {
26
+ // Fallback keeps headless invocation resilient when metadata lookup is unavailable.
27
+ return {
28
+ idAttribute: `${entityName}id`,
29
+ nameAttribute: "name",
30
+ };
31
+ }
32
+ }
33
+ function buildFetchXml(entityName, idAttribute, nameAttribute) {
34
+ const attributeLines = [
35
+ ` <attribute name="${idAttribute}" />`,
36
+ ...(nameAttribute ? [` <attribute name="${nameAttribute}" />`] : []),
37
+ ];
38
+ const orderLine = nameAttribute ? `\n <order attribute="${nameAttribute}" />` : "";
39
+ return `<fetch top="10">\n <entity name="${entityName}">\n${attributeLines.join("\n")}${orderLine}\n </entity>\n</fetch>`;
40
+ }
3
41
  /**
4
42
  * Headless invocation handler.
5
43
  *
@@ -8,26 +46,30 @@
8
46
  */
9
47
  async function invokeHeadless(input, context) {
10
48
  const { toolId, toolName, invocationMode, authToken, updateProgress, logger } = context;
49
+ const dataverseApi = getDataverseApi();
11
50
  logger.info(`Starting headless run for ${toolName} (${toolId}) in mode ${invocationMode}`);
12
51
  updateProgress(10, "validating input");
13
- const entityName = typeof input.entityName === "string" && input.entityName.trim() !== "" ? input.entityName.trim() : "account";
52
+ const entityName = sanitizeEntityName(typeof input.entityName === "string" && input.entityName.trim() !== "" ? input.entityName : "account");
14
53
  if (authToken) {
15
54
  updateProgress(40, "auth token received");
16
55
  }
17
56
  else {
18
57
  updateProgress(40, "running without auth token");
19
58
  }
20
- updateProgress(80, "building FetchXML");
21
- const fetchXml = `<fetch top="10">
22
- <entity name="${entityName}">
23
- <attribute name="name" />
24
- <attribute name="${entityName}id" />
25
- <order attribute="name" />
26
- </entity>
27
- </fetch>`;
59
+ updateProgress(55, "resolving entity metadata");
60
+ const { idAttribute, nameAttribute } = await resolveEntityAttributes(dataverseApi, entityName);
61
+ updateProgress(75, "building FetchXML");
62
+ const fetchXml = buildFetchXml(entityName, idAttribute, nameAttribute);
63
+ updateProgress(90, "executing FetchXML query");
64
+ const result = await dataverseApi.fetchXmlQuery(fetchXml);
28
65
  updateProgress(100, "done");
29
- logger.info(`Headless run complete for entity: ${entityName}`);
30
- return { fetchXml };
66
+ logger.info(`Headless run complete for entity: ${entityName}. Returned ${result.value.length} record(s).`);
67
+ return {
68
+ entityName,
69
+ fetchXml,
70
+ recordCount: result.value.length,
71
+ records: result.value,
72
+ };
31
73
  }
32
74
  module.exports = {
33
75
  invokeHeadless,
package/dist/index.html CHANGED
@@ -177,6 +177,60 @@
177
177
  <h2>⚡ Power Platform API Examples</h2>
178
178
  <p style="margin: 4px 0 15px; font-size: 12px; opacity: 0.85">Generic HTTP methods for Power Platform Admin APIs (Power Apps, Power Automate, Environment Management, Governance).</p>
179
179
 
180
+ <div class="example-group">
181
+ <h3>Generic Endpoint Tester</h3>
182
+ <p style="margin: 4px 0 10px; font-size: 12px; opacity: 0.85">
183
+ Select method + namespace from the namespace catalog and provide a relative path (for example:
184
+ <code>environments?api-version=2024-10-01</code>).
185
+ </p>
186
+ <div class="input-group">
187
+ <label for="pp-generic-method">Method:</label>
188
+ <select id="pp-generic-method">
189
+ <option value="Get" selected>GET</option>
190
+ <option value="Post">POST</option>
191
+ <option value="Put">PUT</option>
192
+ <option value="Patch">PATCH</option>
193
+ <option value="Delete">DELETE</option>
194
+ </select>
195
+ </div>
196
+ <div class="input-group">
197
+ <label for="pp-generic-namespace">Namespace:</label>
198
+ <select id="pp-generic-namespace">
199
+ <option value="Analytics">Analytics</option>
200
+ <option value="AppManagement">AppManagement</option>
201
+ <option value="Authorization">Authorization</option>
202
+ <option value="Connectivity">Connectivity</option>
203
+ <option value="CopilotStudio">CopilotStudio</option>
204
+ <option value="Dynamics">Dynamics</option>
205
+ <option value="EnvironmentManagement" selected>EnvironmentManagement</option>
206
+ <option value="Governance">Governance</option>
207
+ <option value="Licensing">Licensing</option>
208
+ <option value="PowerApps">PowerApps</option>
209
+ <option value="PowerAutomate">PowerAutomate</option>
210
+ <option value="PowerPages">PowerPages</option>
211
+ <option value="ResourceQuery">ResourceQuery</option>
212
+ <option value="UserManagement">UserManagement</option>
213
+ <option value="WorkflowAgents">WorkflowAgents</option>
214
+ </select>
215
+ </div>
216
+ <div class="input-group">
217
+ <label for="pp-generic-path">Relative Path:</label>
218
+ <input type="text" id="pp-generic-path" value="environments?api-version=2024-10-01" placeholder="e.g., environments/{environmentId}/apps?api-version=2024-10-01" />
219
+ </div>
220
+ <div class="input-group">
221
+ <label for="pp-generic-body">Request Body (JSON, optional for POST/PUT/PATCH/DELETE):</label>
222
+ <textarea
223
+ id="pp-generic-body"
224
+ style="width: 100%; min-height: 110px; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; font-family: monospace; font-size: 12px; line-height: 1.4"
225
+ placeholder="{\n \"sample\": \"value\"\n}"
226
+ ></textarea>
227
+ </div>
228
+ <div class="button-group">
229
+ <button id="pp-generic-execute-btn" class="btn btn-primary">Execute Endpoint</button>
230
+ </div>
231
+ <div id="pp-generic-output" class="output"></div>
232
+ </div>
233
+
180
234
  <div class="example-group">
181
235
  <h3>Power Apps API</h3>
182
236
  <div class="input-group">
package/dist/styles.css CHANGED
@@ -6,7 +6,7 @@
6
6
  }
7
7
 
8
8
  body {
9
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
9
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica", "Arial", sans-serif;
10
10
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
11
11
  color: #333;
12
12
  line-height: 1.6;
@@ -91,7 +91,7 @@ header h1 {
91
91
  }
92
92
 
93
93
  .loading::before {
94
- content: '';
94
+ content: "";
95
95
  width: 16px;
96
96
  height: 16px;
97
97
  border: 2px solid #667eea;
@@ -101,7 +101,9 @@ header h1 {
101
101
  }
102
102
 
103
103
  @keyframes spin {
104
- to { transform: rotate(360deg); }
104
+ to {
105
+ transform: rotate(360deg);
106
+ }
105
107
  }
106
108
 
107
109
  /* Connection Status */
@@ -256,6 +258,22 @@ header h1 {
256
258
  border-color: #667eea;
257
259
  }
258
260
 
261
+ .input-group select,
262
+ .input-group textarea {
263
+ width: 100%;
264
+ padding: 10px;
265
+ border: 2px solid #e0e0e0;
266
+ border-radius: 6px;
267
+ font-size: 14px;
268
+ transition: border-color 0.3s ease;
269
+ }
270
+
271
+ .input-group select:focus,
272
+ .input-group textarea:focus {
273
+ outline: none;
274
+ border-color: #667eea;
275
+ }
276
+
259
277
  /* Example Groups */
260
278
  .example-group {
261
279
  margin: 20px 0;
@@ -271,7 +289,7 @@ header h1 {
271
289
  color: #d4d4d4;
272
290
  padding: 15px;
273
291
  border-radius: 6px;
274
- font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
292
+ font-family: "Consolas", "Monaco", "Courier New", monospace;
275
293
  font-size: 13px;
276
294
  white-space: pre-wrap;
277
295
  word-wrap: break-word;
@@ -283,7 +301,7 @@ header h1 {
283
301
 
284
302
  .output:empty::before,
285
303
  .log:empty::before {
286
- content: 'No output yet...';
304
+ content: "No output yet...";
287
305
  color: #666;
288
306
  font-style: italic;
289
307
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pptb-standard-sample-tool",
3
- "version": "1.2.4",
3
+ "version": "1.2.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pptb-standard-sample-tool",
9
- "version": "1.2.4",
9
+ "version": "1.2.8",
10
10
  "license": "GPL-3.0",
11
11
  "devDependencies": {
12
12
  "@pptb/types": "^1.2.4-beta.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptb-standard-sample-tool",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "displayName": "HTML Sample Tool",
5
5
  "description": "A sample Power Platform ToolBox tool built with HTML, CSS, and TypeScript",
6
6
  "main": "index.html",
package/pptb.config.json CHANGED
@@ -13,8 +13,17 @@
13
13
  },
14
14
  "returnTopic": {
15
15
  "properties": {
16
+ "entityName": {
17
+ "type": "string"
18
+ },
16
19
  "fetchXml": {
17
20
  "type": "string"
21
+ },
22
+ "recordCount": {
23
+ "type": "number"
24
+ },
25
+ "records": {
26
+ "type": "array"
18
27
  }
19
28
  }
20
29
  }