@darkwheel/pptb-solution-explorer 0.2.1-beta.0 → 0.2.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/README.md CHANGED
@@ -1,29 +1,29 @@
1
- # Solution Explorer
2
-
3
- A comprehensive Power Platform Tool Box tool for exploring and managing Dataverse solutions.
4
-
5
- ## Features
6
-
7
- - **Solution Listing**: View all solutions in your Dataverse environment
8
- - **Detailed Metadata**: See comprehensive information about each solution including:
9
- - Solution name, version, and description
10
- - Publisher information
11
- - Installation and modification dates
12
- - Managed vs. unmanaged status
13
- - Solution components count
14
- - Package type and version information
15
- - **Publisher Details**: View publisher information including prefix and option value prefix
16
- - **Filter & Search**: Easily find specific solutions
17
- - **Management Ready**: Quick access to solution metadata for administrative tasks
18
-
19
- ## Usage
20
-
21
- 1. Connect to your Dataverse environment in Power Platform Tool Box
22
- 2. Open the Solution Explorer tool
23
- 3. Click "Load Solutions" to retrieve all solutions
24
- 4. Click on any solution to view detailed information
25
-
26
- ## Requirements
27
-
28
- - Power Platform Tool Box
1
+ # Solution Explorer
2
+
3
+ A comprehensive Power Platform Tool Box tool for exploring and managing Dataverse solutions.
4
+
5
+ ## Features
6
+
7
+ - **Solution Listing**: View all solutions in your Dataverse environment
8
+ - **Detailed Metadata**: See comprehensive information about each solution including:
9
+ - Solution name, version, and description
10
+ - Publisher information
11
+ - Installation and modification dates
12
+ - Managed vs. unmanaged status
13
+ - Solution components count
14
+ - Package type and version information
15
+ - **Publisher Details**: View publisher information including prefix and option value prefix
16
+ - **Filter & Search**: Easily find specific solutions
17
+ - **Management Ready**: Quick access to solution metadata for administrative tasks
18
+
19
+ ## Usage
20
+
21
+ 1. Connect to your Dataverse environment in Power Platform Tool Box
22
+ 2. Open the Solution Explorer tool
23
+ 3. Click "Load Solutions" to retrieve all solutions
24
+ 4. Click on any solution to view detailed information
25
+
26
+ ## Requirements
27
+
28
+ - Power Platform Tool Box
29
29
  - Dataverse connection with appropriate permissions to read solution metadata
package/dist/app.js CHANGED
@@ -123,38 +123,38 @@ async function loadSolutions() {
123
123
  await toolbox.utils.showLoading('Loading solutions...');
124
124
  // FetchXML to get solutions with publisher information (dynamically set top value)
125
125
  const topAttribute = currentLimit === 'all' ? '' : `top="${currentLimit}"`;
126
- const fetchXml = `
127
- <fetch ${topAttribute}>
128
- <entity name="solution">
129
- <attribute name="solutionid" />
130
- <attribute name="uniquename" />
131
- <attribute name="friendlyname" />
132
- <attribute name="version" />
133
- <attribute name="description" />
134
- <attribute name="installedon" />
135
- <attribute name="createdon" />
136
- <attribute name="modifiedon" />
137
- <attribute name="ismanaged" />
138
- <attribute name="isvisible" />
139
- <attribute name="solutionpackageversion" />
140
- <attribute name="solutiontype" />
141
- <attribute name="publisherid" />
142
- <link-entity name="publisher" from="publisherid" to="publisherid" alias="publisher">
143
- <attribute name="friendlyname" />
144
- <attribute name="uniquename" />
145
- <attribute name="customizationprefix" />
146
- <attribute name="customizationoptionvalueprefix" />
147
- <attribute name="description" />
148
- </link-entity>
149
- <link-entity name="systemuser" from="systemuserid" to="modifiedby" alias="modifiedby">
150
- <attribute name="fullname" />
151
- <attribute name="domainname" />
152
- </link-entity>
153
- <filter>
154
- <condition attribute="isvisible" operator="eq" value="1" />
155
- </filter>
156
- <order attribute="installedon" descending="true" />
157
- </entity>
126
+ const fetchXml = `
127
+ <fetch ${topAttribute}>
128
+ <entity name="solution">
129
+ <attribute name="solutionid" />
130
+ <attribute name="uniquename" />
131
+ <attribute name="friendlyname" />
132
+ <attribute name="version" />
133
+ <attribute name="description" />
134
+ <attribute name="installedon" />
135
+ <attribute name="createdon" />
136
+ <attribute name="modifiedon" />
137
+ <attribute name="ismanaged" />
138
+ <attribute name="isvisible" />
139
+ <attribute name="solutionpackageversion" />
140
+ <attribute name="solutiontype" />
141
+ <attribute name="publisherid" />
142
+ <link-entity name="publisher" from="publisherid" to="publisherid" alias="publisher">
143
+ <attribute name="friendlyname" />
144
+ <attribute name="uniquename" />
145
+ <attribute name="customizationprefix" />
146
+ <attribute name="customizationoptionvalueprefix" />
147
+ <attribute name="description" />
148
+ </link-entity>
149
+ <link-entity name="systemuser" from="systemuserid" to="modifiedby" alias="modifiedby">
150
+ <attribute name="fullname" />
151
+ <attribute name="domainname" />
152
+ </link-entity>
153
+ <filter>
154
+ <condition attribute="isvisible" operator="eq" value="1" />
155
+ </filter>
156
+ <order attribute="installedon" descending="true" />
157
+ </entity>
158
158
  </fetch>`;
159
159
  const result = await dataverse.fetchXmlQuery(fetchXml);
160
160
  allSolutions = result.value;
@@ -235,18 +235,18 @@ function displaySolutions() {
235
235
  const isManaged = solution.ismanaged;
236
236
  const typeLabel = isManaged ? 'Managed' : 'Unmanaged';
237
237
  const typeClass = isManaged ? 'managed' : 'unmanaged';
238
- return `
239
- <div class="solution-card ${typeClass}" data-solutionid="${solution.solutionid}">
240
- <div class="solution-header">
241
- <div class="solution-name">${escapeHtml(solution.friendlyname || solution.uniquename || 'N/A')}</div>
242
- <span class="solution-type-badge ${typeClass}">${typeLabel}</span>
243
- </div>
244
- <div class="solution-meta">
245
- <div class="solution-version">v${escapeHtml(solution.version || '1.0.0.0')}</div>
246
- <div class="solution-publisher">${escapeHtml(solution['publisher.friendlyname'] || 'N/A')}</div>
247
- </div>
248
- <div class="solution-unique-name">${escapeHtml(solution.uniquename || 'N/A')}</div>
249
- </div>
238
+ return `
239
+ <div class="solution-card ${typeClass}" data-solutionid="${solution.solutionid}">
240
+ <div class="solution-header">
241
+ <div class="solution-name">${escapeHtml(solution.friendlyname || solution.uniquename || 'N/A')}</div>
242
+ <span class="solution-type-badge ${typeClass}">${typeLabel}</span>
243
+ </div>
244
+ <div class="solution-meta">
245
+ <div class="solution-version">v${escapeHtml(solution.version || '1.0.0.0')}</div>
246
+ <div class="solution-publisher">${escapeHtml(solution['publisher.friendlyname'] || 'N/A')}</div>
247
+ </div>
248
+ <div class="solution-unique-name">${escapeHtml(solution.uniquename || 'N/A')}</div>
249
+ </div>
250
250
  `;
251
251
  }).join('');
252
252
  // Add click handlers
@@ -298,14 +298,14 @@ async function selectSolution(solutionId, solutionData) {
298
298
  */
299
299
  async function getSolutionComponentCount(solutionId) {
300
300
  try {
301
- const fetchXml = `
302
- <fetch aggregate="true">
303
- <entity name="solutioncomponent">
304
- <attribute name="solutioncomponentid" alias="count" aggregate="count" />
305
- <filter>
306
- <condition attribute="solutionid" operator="eq" value="${solutionId}" />
307
- </filter>
308
- </entity>
301
+ const fetchXml = `
302
+ <fetch aggregate="true">
303
+ <entity name="solutioncomponent">
304
+ <attribute name="solutioncomponentid" alias="count" aggregate="count" />
305
+ <filter>
306
+ <condition attribute="solutionid" operator="eq" value="${solutionId}" />
307
+ </filter>
308
+ </entity>
309
309
  </fetch>`;
310
310
  const result = await dataverse.fetchXmlQuery(fetchXml);
311
311
  return parseInt(result.value[0]?.count || '0', 10);
@@ -320,22 +320,22 @@ async function getSolutionComponentCount(solutionId) {
320
320
  */
321
321
  async function getSolutionDeploymentHistory(solutionId) {
322
322
  try {
323
- const fetchXml = `
324
- <fetch top="20">
325
- <entity name="importjob">
326
- <attribute name="importjobid" />
327
- <attribute name="solutionname" />
328
- <attribute name="completedon" />
329
- <attribute name="startedon" />
330
- <attribute name="progress" />
331
- <attribute name="createdonbehalfby" />
332
- <attribute name="createdby" />
333
- <attribute name="modifiedby" />
334
- <filter>
335
- <condition attribute="solutionid" operator="eq" value="${solutionId}" />
336
- </filter>
337
- <order attribute="completedon" descending="true" />
338
- </entity>
323
+ const fetchXml = `
324
+ <fetch top="20">
325
+ <entity name="importjob">
326
+ <attribute name="importjobid" />
327
+ <attribute name="solutionname" />
328
+ <attribute name="completedon" />
329
+ <attribute name="startedon" />
330
+ <attribute name="progress" />
331
+ <attribute name="createdonbehalfby" />
332
+ <attribute name="createdby" />
333
+ <attribute name="modifiedby" />
334
+ <filter>
335
+ <condition attribute="solutionid" operator="eq" value="${solutionId}" />
336
+ </filter>
337
+ <order attribute="completedon" descending="true" />
338
+ </entity>
339
339
  </fetch>`;
340
340
  const result = await dataverse.fetchXmlQuery(fetchXml);
341
341
  return result.value || [];
@@ -355,162 +355,162 @@ function displaySolutionDetails(solution, componentCount, deploymentHistory = []
355
355
  const isManaged = solution.ismanaged;
356
356
  const typeLabel = isManaged ? 'Managed' : 'Unmanaged';
357
357
  const typeClass = isManaged ? 'managed' : 'unmanaged';
358
- detailsPanel.innerHTML = `
359
- <div class="solution-details">
360
- <div class="details-header">
361
- <h3>${escapeHtml(solution.friendlyname || solution.uniquename || 'N/A')}</h3>
362
- <span class="solution-type-badge ${typeClass}">${typeLabel}</span>
363
- </div>
364
-
365
- <div class="details-section">
366
- <h4>Basic Information</h4>
367
- <table class="details-table">
368
- <tr>
369
- <td class="label">Display Name:</td>
370
- <td>${escapeHtml(solution.friendlyname || 'N/A')}</td>
371
- </tr>
372
- <tr>
373
- <td class="label">Unique Name:</td>
374
- <td><code>${escapeHtml(solution.uniquename || 'N/A')}</code></td>
375
- </tr>
376
- <tr>
377
- <td class="label">Version:</td>
378
- <td>${escapeHtml(solution.version || '1.0.0.0')}</td>
379
- </tr>
380
- <tr>
381
- <td class="label">Description:</td>
382
- <td>${escapeHtml(solution.description || 'No description provided')}</td>
383
- </tr>
384
- <tr>
385
- <td class="label">Solution ID:</td>
386
- <td><code>${escapeHtml(solution.solutionid || 'N/A')}</code></td>
387
- </tr>
388
- </table>
389
- </div>
390
-
391
- <div class="details-section">
392
- <h4>Publisher Information</h4>
393
- <table class="details-table">
394
- <tr>
395
- <td class="label">Publisher Name:</td>
396
- <td>${escapeHtml(solution['publisher.friendlyname'] || 'N/A')}</td>
397
- </tr>
398
- <tr>
399
- <td class="label">Publisher Unique Name:</td>
400
- <td><code>${escapeHtml(solution['publisher.uniquename'] || 'N/A')}</code></td>
401
- </tr>
402
- <tr>
403
- <td class="label">Customization Prefix:</td>
404
- <td><code>${escapeHtml(solution['publisher.customizationprefix'] || 'N/A')}</code></td>
405
- </tr>
406
- <tr>
407
- <td class="label">Option Value Prefix:</td>
408
- <td>${solution['publisher.customizationoptionvalueprefix'] !== null && solution['publisher.customizationoptionvalueprefix'] !== undefined ? solution['publisher.customizationoptionvalueprefix'] : 'N/A'}</td>
409
- </tr>
410
- ${solution['publisher.description'] ? `
411
- <tr>
412
- <td class="label">Publisher Description:</td>
413
- <td>${escapeHtml(solution['publisher.description'])}</td>
414
- </tr>
415
- ` : ''}
416
- </table>
417
- </div>
418
-
419
- <div class="details-section">
420
- <h4>Installation & Deployment</h4>
421
- <table class="details-table">
422
- <tr>
423
- <td class="label">Installed On:</td>
424
- <td>${solution.installedon ? formatDateTime(solution.installedon) : 'N/A'}</td>
425
- </tr>
426
- <tr>
427
- <td class="label">Created On:</td>
428
- <td>${solution.createdon ? formatDateTime(solution.createdon) : 'N/A'}</td>
429
- </tr>
430
- <tr>
431
- <td class="label">Modified On:</td>
432
- <td>${solution.modifiedon ? formatDateTime(solution.modifiedon) : 'N/A'}</td>
433
- </tr>
434
- <tr>
435
- <td class="label">Last Modified By:</td>
436
- <td>
437
- <div class="modifier-info">
438
- <div class="modifier-name">${escapeHtml(solution['modifiedby.fullname'] || 'N/A')}</div>
439
- ${solution['modifiedby.domainname'] ? `<div class="modifier-domain">${escapeHtml(solution['modifiedby.domainname'])}</div>` : ''}
440
- </div>
441
- </td>
442
- </tr>
443
- <tr>
444
- <td class="label">Package Type:</td>
445
- <td>${isManaged ? 'Managed' : 'Unmanaged'}</td>
446
- </tr>
447
- <tr>
448
- <td class="label">Is Visible:</td>
449
- <td>${solution.isvisible ? 'Yes' : 'No'}</td>
450
- </tr>
451
- ${solution.solutionpackageversion ? `
452
- <tr>
453
- <td class="label">Package Version:</td>
454
- <td>${escapeHtml(solution.solutionpackageversion)}</td>
455
- </tr>
456
- ` : ''}
457
- </table>
458
- </div>
459
-
460
- <div class="details-section">
461
- <h4>Components</h4>
462
- <table class="details-table">
463
- <tr>
464
- <td class="label">Component Count:</td>
465
- <td><strong>${componentCount}</strong> component(s)</td>
466
- </tr>
467
- </table>
468
- </div>
469
-
470
- ${deploymentHistory.length > 0 ? `
471
- <div class="details-section">
472
- <h4>Deployment History</h4>
473
- <div class="deployment-history">
358
+ detailsPanel.innerHTML = `
359
+ <div class="solution-details">
360
+ <div class="details-header">
361
+ <h3>${escapeHtml(solution.friendlyname || solution.uniquename || 'N/A')}</h3>
362
+ <span class="solution-type-badge ${typeClass}">${typeLabel}</span>
363
+ </div>
364
+
365
+ <div class="details-section">
366
+ <h4>Basic Information</h4>
367
+ <table class="details-table">
368
+ <tr>
369
+ <td class="label">Display Name:</td>
370
+ <td>${escapeHtml(solution.friendlyname || 'N/A')}</td>
371
+ </tr>
372
+ <tr>
373
+ <td class="label">Unique Name:</td>
374
+ <td><code>${escapeHtml(solution.uniquename || 'N/A')}</code></td>
375
+ </tr>
376
+ <tr>
377
+ <td class="label">Version:</td>
378
+ <td>${escapeHtml(solution.version || '1.0.0.0')}</td>
379
+ </tr>
380
+ <tr>
381
+ <td class="label">Description:</td>
382
+ <td>${escapeHtml(solution.description || 'No description provided')}</td>
383
+ </tr>
384
+ <tr>
385
+ <td class="label">Solution ID:</td>
386
+ <td><code>${escapeHtml(solution.solutionid || 'N/A')}</code></td>
387
+ </tr>
388
+ </table>
389
+ </div>
390
+
391
+ <div class="details-section">
392
+ <h4>Publisher Information</h4>
393
+ <table class="details-table">
394
+ <tr>
395
+ <td class="label">Publisher Name:</td>
396
+ <td>${escapeHtml(solution['publisher.friendlyname'] || 'N/A')}</td>
397
+ </tr>
398
+ <tr>
399
+ <td class="label">Publisher Unique Name:</td>
400
+ <td><code>${escapeHtml(solution['publisher.uniquename'] || 'N/A')}</code></td>
401
+ </tr>
402
+ <tr>
403
+ <td class="label">Customization Prefix:</td>
404
+ <td><code>${escapeHtml(solution['publisher.customizationprefix'] || 'N/A')}</code></td>
405
+ </tr>
406
+ <tr>
407
+ <td class="label">Option Value Prefix:</td>
408
+ <td>${solution['publisher.customizationoptionvalueprefix'] !== null && solution['publisher.customizationoptionvalueprefix'] !== undefined ? solution['publisher.customizationoptionvalueprefix'] : 'N/A'}</td>
409
+ </tr>
410
+ ${solution['publisher.description'] ? `
411
+ <tr>
412
+ <td class="label">Publisher Description:</td>
413
+ <td>${escapeHtml(solution['publisher.description'])}</td>
414
+ </tr>
415
+ ` : ''}
416
+ </table>
417
+ </div>
418
+
419
+ <div class="details-section">
420
+ <h4>Installation & Deployment</h4>
421
+ <table class="details-table">
422
+ <tr>
423
+ <td class="label">Installed On:</td>
424
+ <td>${solution.installedon ? formatDateTime(solution.installedon) : 'N/A'}</td>
425
+ </tr>
426
+ <tr>
427
+ <td class="label">Created On:</td>
428
+ <td>${solution.createdon ? formatDateTime(solution.createdon) : 'N/A'}</td>
429
+ </tr>
430
+ <tr>
431
+ <td class="label">Modified On:</td>
432
+ <td>${solution.modifiedon ? formatDateTime(solution.modifiedon) : 'N/A'}</td>
433
+ </tr>
434
+ <tr>
435
+ <td class="label">Last Modified By:</td>
436
+ <td>
437
+ <div class="modifier-info">
438
+ <div class="modifier-name">${escapeHtml(solution['modifiedby.fullname'] || 'N/A')}</div>
439
+ ${solution['modifiedby.domainname'] ? `<div class="modifier-domain">${escapeHtml(solution['modifiedby.domainname'])}</div>` : ''}
440
+ </div>
441
+ </td>
442
+ </tr>
443
+ <tr>
444
+ <td class="label">Package Type:</td>
445
+ <td>${isManaged ? 'Managed' : 'Unmanaged'}</td>
446
+ </tr>
447
+ <tr>
448
+ <td class="label">Is Visible:</td>
449
+ <td>${solution.isvisible ? 'Yes' : 'No'}</td>
450
+ </tr>
451
+ ${solution.solutionpackageversion ? `
452
+ <tr>
453
+ <td class="label">Package Version:</td>
454
+ <td>${escapeHtml(solution.solutionpackageversion)}</td>
455
+ </tr>
456
+ ` : ''}
457
+ </table>
458
+ </div>
459
+
460
+ <div class="details-section">
461
+ <h4>Components</h4>
462
+ <table class="details-table">
463
+ <tr>
464
+ <td class="label">Component Count:</td>
465
+ <td><strong>${componentCount}</strong> component(s)</td>
466
+ </tr>
467
+ </table>
468
+ </div>
469
+
470
+ ${deploymentHistory.length > 0 ? `
471
+ <div class="details-section">
472
+ <h4>Deployment History</h4>
473
+ <div class="deployment-history">
474
474
  ${deploymentHistory.map((deployment, index) => {
475
475
  const completedOn = deployment.completedon ? formatDateTime(deployment.completedon) : 'N/A';
476
476
  const startedOn = deployment.startedon ? formatDateTime(deployment.startedon) : 'N/A';
477
477
  const deployedBy = escapeHtml(deployment['_createdby_value@OData.Community.Display.V1.FormattedValue'] || 'System');
478
478
  const progress = deployment.progress !== null && deployment.progress !== undefined ? Math.round(deployment.progress) : 100;
479
- return `
480
- <div class="deployment-item">
481
- <div class="deployment-header">
482
- <div class="deployment-number">#${index + 1}</div>
483
- <div class="deployment-date">${completedOn}</div>
484
- </div>
485
- <div class="deployment-details">
486
- <div class="deployment-info">
487
- <span class="label">Deployed By:</span>
488
- <span class="value">${deployedBy}</span>
489
- </div>
490
- <div class="deployment-info">
491
- <span class="label">Started:</span>
492
- <span class="value">${startedOn}</span>
493
- </div>
494
- <div class="deployment-info">
495
- <span class="label">Progress:</span>
496
- <span class="value">${progress}%</span>
497
- </div>
498
- </div>
499
- </div>
479
+ return `
480
+ <div class="deployment-item">
481
+ <div class="deployment-header">
482
+ <div class="deployment-number">#${index + 1}</div>
483
+ <div class="deployment-date">${completedOn}</div>
484
+ </div>
485
+ <div class="deployment-details">
486
+ <div class="deployment-info">
487
+ <span class="label">Deployed By:</span>
488
+ <span class="value">${deployedBy}</span>
489
+ </div>
490
+ <div class="deployment-info">
491
+ <span class="label">Started:</span>
492
+ <span class="value">${startedOn}</span>
493
+ </div>
494
+ <div class="deployment-info">
495
+ <span class="label">Progress:</span>
496
+ <span class="value">${progress}%</span>
497
+ </div>
498
+ </div>
499
+ </div>
500
500
  `;
501
- }).join('')}
502
- </div>
503
- </div>
504
- ` : ''}
505
-
506
- <div class="details-section">
507
- <h4>Management Actions</h4>
508
- <div class="action-buttons">
509
- <button class="btn btn-secondary btn-small" onclick="copySolutionId('${solution.solutionid}')">Copy Solution ID</button>
510
- <button class="btn btn-secondary btn-small" onclick="copySolutionUniqueName('${solution.uniquename}')">Copy Unique Name</button>
511
- </div>
512
- </div>
513
- </div>
501
+ }).join('')}
502
+ </div>
503
+ </div>
504
+ ` : ''}
505
+
506
+ <div class="details-section">
507
+ <h4>Management Actions</h4>
508
+ <div class="action-buttons">
509
+ <button class="btn btn-secondary btn-small" onclick="copySolutionId('${solution.solutionid}')">Copy Solution ID</button>
510
+ <button class="btn btn-secondary btn-small" onclick="copySolutionUniqueName('${solution.uniquename}')">Copy Unique Name</button>
511
+ </div>
512
+ </div>
513
+ </div>
514
514
  `;
515
515
  }
516
516
  /**
@@ -582,9 +582,9 @@ function log(message, type = 'info') {
582
582
  const timestamp = new Date().toLocaleTimeString();
583
583
  const logEntry = document.createElement('div');
584
584
  logEntry.className = `log-entry ${type}`;
585
- logEntry.innerHTML = `
586
- <span class="log-timestamp">[${timestamp}]</span>
587
- <span>${message}</span>
585
+ logEntry.innerHTML = `
586
+ <span class="log-timestamp">[${timestamp}]</span>
587
+ <span>${message}</span>
588
588
  `;
589
589
  logDiv.insertBefore(logEntry, logDiv.firstChild);
590
590
  // Keep only last 50 entries