Skip to content

Commit 4231d3d

Browse files
authored
Enhance website management features (#1155)
- ✨ Added new properties `createdOn` and `creator` to website details. - 🔄 Refactored site visibility handling to use an enum. - 🛠️ Updated various components to utilize new properties. - ✅ Enhanced tests to cover new functionality. -Priyanshu
1 parent f90828c commit 4231d3d

16 files changed

Lines changed: 282 additions & 92 deletions

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
22
"cSpell.words": [
33
"appmoduleid",
4+
"createdon",
45
"dataverse",
56
"entityrecord",
67
"Entra",
78
"lcid",
89
"MSPP",
910
"nupkg",
11+
"owninguser",
1012
"pagetype",
1113
"powerapps",
1214
"powerpages",

src/client/power-pages/actions-hub/ActionsHubCommandHandlers.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { extractAuthInfo } from '../commonUtility';
1515
import { showProgressWithNotification } from '../../../common/utilities/Utils';
1616
import PacContext from '../../pac/PacContext';
1717
import ArtemisContext from '../../ArtemisContext';
18-
import { ServiceEndpointCategory } from '../../../common/services/Constants';
18+
import { ServiceEndpointCategory, WebsiteDataModel } from '../../../common/services/Constants';
1919
import { SiteTreeItem } from './tree-items/SiteTreeItem';
2020
import { PreviewSite } from '../preview-site/PreviewSite';
2121
import { PacWrapper } from '../../pac/PacWrapper';
@@ -28,6 +28,8 @@ import path from 'path';
2828
import { getWebsiteRecordId } from '../../../common/utilities/WorkspaceInfoFinderUtil';
2929
import { isEdmEnvironment } from '../../../common/copilot/dataverseMetadata';
3030
import { IWebsiteInfo } from './models/IWebsiteInfo';
31+
import moment from 'moment';
32+
import { SiteVisibility } from './models/SiteVisibility';
3133

3234
export const refreshEnvironment = async (pacTerminal: PacTerminal) => {
3335
const pacWrapper = pacTerminal.getWrapper();
@@ -218,7 +220,20 @@ export const fetchWebsites = async (): Promise<{ activeSites: IWebsiteDetails[],
218220
]);
219221
const activeSiteIds = new Set(activeWebsiteDetails.map(activeSite => activeSite.websiteRecordId));
220222
const inactiveWebsiteDetails = allSites?.filter(site => !activeSiteIds.has(site.websiteRecordId)) || [];
221-
activeWebsiteDetails = activeWebsiteDetails.map(detail => ({ ...detail, siteManagementUrl: allSites.find(site => site.websiteRecordId === detail.websiteRecordId)?.siteManagementUrl ?? "" }));
223+
activeWebsiteDetails = activeWebsiteDetails.map(detail => {
224+
const site = allSites.find(site => site.websiteRecordId === detail.websiteRecordId);
225+
226+
if (!site) {
227+
return detail;
228+
}
229+
230+
return {
231+
...detail,
232+
siteManagementUrl: site.siteManagementUrl,
233+
createdOn: site.createdOn,
234+
creator: site.creator,
235+
}
236+
});
222237

223238
const currentEnvSiteIds = createKnownSiteIdsSet(activeWebsiteDetails, inactiveWebsiteDetails);
224239
const otherSites = findOtherSites(currentEnvSiteIds);
@@ -323,7 +338,7 @@ async function uploadOtherSite(siteTreeItem: SiteTreeItem): Promise<void> {
323338
*/
324339
async function uploadCurrentSite(siteTreeItem: SiteTreeItem, websitePath: string): Promise<void> {
325340
// Public sites require confirmation to prevent accidental deployment
326-
if (siteTreeItem.siteInfo.siteVisibility?.toLowerCase() === Constants.SiteVisibility.PUBLIC) {
341+
if (siteTreeItem.siteInfo.siteVisibility?.toLowerCase() === SiteVisibility.Public) {
327342
const confirm = await vscode.window.showInformationMessage(
328343
Constants.Strings.SITE_UPLOAD_CONFIRMATION,
329344
{ modal: true },
@@ -456,14 +471,28 @@ export const showSiteDetails = async (siteTreeItem: SiteTreeItem) => {
456471
const siteInfo = siteTreeItem.siteInfo;
457472
const details = [
458473
vscode.l10n.t({ message: "Friendly name: {0}", args: [siteInfo.name], comment: "{0} is the website name" }),
459-
vscode.l10n.t({ message: "Website ID: {0}", args: [siteInfo.websiteId], comment: "{0} is the website ID" }),
460-
vscode.l10n.t({ message: "Data model version: v{0}", args: [siteInfo.dataModelVersion], comment: "{0} is the data model version" })
461-
].join('\n');
474+
vscode.l10n.t({ message: "Website Id: {0}", args: [siteInfo.websiteId], comment: "{0} is the website ID" }),
475+
vscode.l10n.t({ message: "Data model version: {0}", args: [siteInfo.dataModelVersion === 1 ? WebsiteDataModel.Standard : WebsiteDataModel.Enhanced], comment: "{0} is the data model version" })
476+
];
477+
478+
if (siteInfo.websiteUrl) {
479+
details.push(vscode.l10n.t({ message: "Website Url: {0}", args: [siteInfo.websiteUrl], comment: "{0} is the website Url" }));
480+
}
481+
482+
if (siteInfo.siteVisibility) {
483+
const visibility = siteInfo.siteVisibility.charAt(0).toUpperCase() + siteInfo.siteVisibility.slice(1).toLowerCase();
484+
details.push(vscode.l10n.t({ message: "Site visibility: {0}", args: [visibility], comment: "{0} is the site visibility" }));
485+
}
486+
487+
details.push(vscode.l10n.t({ message: "Creator: {0}", args: [siteInfo.creator], comment: "{0} is the creator" }));
488+
details.push(vscode.l10n.t({ message: "Created on: {0}", args: [moment(siteInfo.createdOn).format('LL')], comment: "{0} is the created date" }));
489+
490+
const formattedDetails = details.join('\n');
462491

463-
const result = await vscode.window.showInformationMessage(Constants.Strings.SITE_DETAILS, { detail: details, modal: true }, Constants.Strings.COPY_TO_CLIPBOARD);
492+
const result = await vscode.window.showInformationMessage(Constants.Strings.SITE_DETAILS, { detail: formattedDetails, modal: true }, Constants.Strings.COPY_TO_CLIPBOARD);
464493

465494
if (result === Constants.Strings.COPY_TO_CLIPBOARD) {
466-
await vscode.env.clipboard.writeText(details);
495+
await vscode.env.clipboard.writeText(formattedDetails);
467496
}
468497
}
469498

src/client/power-pages/actions-hub/Constants.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ export const Constants = {
6868
HIGH: "https://make.high.powerpages.microsoft.us",
6969
MOONCAKE: "https://make.powerpages.microsoft.cn"
7070
},
71-
SiteVisibility : {
72-
PUBLIC: "public",
73-
PRIVATE: "private"
74-
},
7571
AppNames: {
7672
POWER_PAGES_MANAGEMENT: 'mspp_powerpagemanagement',
7773
PORTAL_MANAGEMENT: 'dynamics365portals'

src/client/power-pages/actions-hub/models/IWebsiteInfo.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*/
55

6+
import { SiteVisibility } from "./SiteVisibility";
67
import { WebsiteStatus } from "./WebsiteStatus";
78

89
export interface IWebsiteInfo {
@@ -12,7 +13,9 @@ export interface IWebsiteInfo {
1213
websiteUrl: string;
1314
status: WebsiteStatus | undefined;
1415
isCurrent: boolean;
15-
siteVisibility: string;
16+
siteVisibility: SiteVisibility | undefined;
1617
siteManagementUrl: string;
17-
folderPath?: string;
18+
folderPath?: string;
19+
creator: string;
20+
createdOn: string;
1821
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*/
5+
6+
export enum SiteVisibility {
7+
Public = "public",
8+
Private = "private"
9+
}

src/client/power-pages/actions-hub/tree-items/ActiveGroupTreeItem.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ export class ActiveGroupTreeItem extends ActionsHubTreeItem {
4040
websiteUrl: site.websiteUrl,
4141
status: WebsiteStatus.Active,
4242
isCurrent: this.isCurrentSite(site.websiteRecordId),
43-
siteVisibility: site.siteVisibility ?? "",
44-
siteManagementUrl: site.siteManagementUrl
43+
siteVisibility: site.siteVisibility,
44+
siteManagementUrl: site.siteManagementUrl,
45+
creator: site.creator,
46+
createdOn: site.createdOn
4547
};
4648

4749
return new SiteTreeItem(siteInfo);

src/client/power-pages/actions-hub/tree-items/InactiveGroupTreeItem.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ export class InactiveGroupTreeItem extends ActionsHubTreeItem {
3939
websiteUrl: site.websiteUrl,
4040
status: WebsiteStatus.Inactive,
4141
isCurrent: false,
42-
siteVisibility: "",
43-
siteManagementUrl: site.siteManagementUrl
42+
siteVisibility: undefined,
43+
siteManagementUrl: site.siteManagementUrl,
44+
creator: site.creator,
45+
createdOn: site.createdOn
4446
};
4547
const siteItem = new SiteTreeItem(siteInfo);
4648
return siteItem;

src/client/power-pages/actions-hub/tree-items/OtherSitesGroupTreeItem.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ export class OtherSitesGroupTreeItem extends ActionsHubTreeItem {
3333
status: undefined,
3434
isCurrent: false,
3535
websiteUrl: "",
36-
siteVisibility: "",
36+
siteVisibility: undefined,
3737
siteManagementUrl: "",
38-
folderPath : site.folderPath
38+
folderPath : site.folderPath,
39+
createdOn: "",
40+
creator: ""
3941
};
4042
return new SiteTreeItem(siteInfo);
4143
});

src/client/power-pages/preview-site/PreviewSite.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import CurrentSiteContext from '../actions-hub/CurrentSiteContext';
2323
import { IWebsiteDetails } from '../../../common/services/Interfaces';
2424
import { uploadSite } from '../actions-hub/ActionsHubCommandHandlers';
2525
import { SiteTreeItem } from '../actions-hub/tree-items/SiteTreeItem';
26+
import { SiteVisibility } from '../actions-hub/models/SiteVisibility';
2627

2728
export const SITE_PREVIEW_COMMAND_ID = "microsoft.powerplatform.pages.preview-site";
2829

@@ -116,8 +117,8 @@ export class PreviewSite {
116117
}
117118
}
118119

119-
public static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string | undefined, dataModelVersion: 1 | 2, siteVisibility: string): Promise<void> {
120-
if (!webSitePreviewURL || webSitePreviewURL === "") {
120+
public static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string | undefined, dataModelVersion: 1 | 2, siteVisibility: SiteVisibility | undefined): Promise<void> {
121+
if (!webSitePreviewURL || webSitePreviewURL === "" || !siteVisibility) {
121122
return;
122123
}
123124

@@ -139,7 +140,7 @@ export class PreviewSite {
139140
}
140141
}
141142

142-
private static async showUploadWarning(websitePath: string, dataModelVersion: 1 | 2, siteVisibility: string) {
143+
private static async showUploadWarning(websitePath: string, dataModelVersion: 1 | 2, siteVisibility: SiteVisibility) {
143144
const pendingChangesResult = await PreviewSite._pacTerminal.getWrapper().pendingChanges(websitePath, dataModelVersion);
144145

145146
try {
@@ -206,7 +207,7 @@ export class PreviewSite {
206207
await PreviewSite.launchBrowserAndDevToolsWithinVsCode(
207208
PreviewSite._websiteDetails.websiteUrl,
208209
PreviewSite._websiteDetails.dataModel === WebsiteDataModel.Standard ? 1 : 2,
209-
PreviewSite._websiteDetails.siteVisibility ?? ""
210+
PreviewSite._websiteDetails.siteVisibility
210211
);
211212
}
212213

src/client/test/Integration/power-pages/actions-hub/ActionsHubCommandHandlers.test.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import * as Utils from '../../../../../common/utilities/Utils';
2828
import CurrentSiteContext from '../../../../power-pages/actions-hub/CurrentSiteContext';
2929
import * as WorkspaceInfoFinderUtil from "../../../../../common/utilities/WorkspaceInfoFinderUtil";
3030
import path from 'path';
31+
import { SiteVisibility } from '../../../../power-pages/actions-hub/models/SiteVisibility';
3132

3233
describe('ActionsHubCommandHandlers', () => {
3334
let sandbox: sinon.SinonSandbox;
@@ -609,8 +610,10 @@ describe('ActionsHubCommandHandlers', () => {
609610
status: WebsiteStatus.Active,
610611
websiteUrl: 'https://test-site.com',
611612
isCurrent: false,
612-
siteVisibility: 'Public',
613+
siteVisibility: SiteVisibility.Public,
613614
siteManagementUrl: 'https://test-site-management.com',
615+
createdOn: "2025-03-20",
616+
creator: "Test Creator"
614617
};
615618
});
616619

@@ -699,7 +702,7 @@ describe('ActionsHubCommandHandlers', () => {
699702
] as IWebsiteDetails[];
700703

701704
const allSites = [
702-
...activeSites.map(site => ({ ...site, siteManagementUrl: "https://portalmanagement.com" })),
705+
...activeSites.map(site => ({ ...site, siteManagementUrl: "https://portalmanagement.com", createdOn: "2025-03-20", creator: "Test Creator" })),
703706
...inactiveSites
704707

705708
] as IWebsiteDetails[];
@@ -708,7 +711,7 @@ describe('ActionsHubCommandHandlers', () => {
708711

709712
const response = await fetchWebsites();
710713

711-
expect(response.activeSites).to.deep.equal([...activeSites.map(site => ({ ...site, siteManagementUrl: "https://portalmanagement.com" }))]);
714+
expect(response.activeSites).to.deep.equal([...activeSites.map(site => ({ ...site, siteManagementUrl: "https://portalmanagement.com", createdOn: "2025-03-20", creator: "Test Creator" }))]);
712715
expect(response.inactiveSites).to.deep.equal(inactiveSites);
713716
});
714717
});
@@ -774,8 +777,10 @@ describe('ActionsHubCommandHandlers', () => {
774777
status: WebsiteStatus.Active,
775778
websiteUrl: 'https://test-site.com',
776779
isCurrent: false,
777-
siteVisibility: Constants.SiteVisibility.PRIVATE,
778-
siteManagementUrl: "https://test-site-management.com"
780+
siteVisibility: SiteVisibility.Private,
781+
siteManagementUrl: "https://test-site-management.com",
782+
createdOn: "2025-03-20",
783+
creator: "Test Creator"
779784
} as IWebsiteInfo
780785
} as SiteTreeItem);
781786

@@ -794,7 +799,7 @@ describe('ActionsHubCommandHandlers', () => {
794799
status: WebsiteStatus.Active,
795800
websiteUrl: 'https://test-site.com',
796801
isCurrent: false,
797-
siteVisibility: Constants.SiteVisibility.PRIVATE
802+
siteVisibility: SiteVisibility.Private,
798803
} as IWebsiteInfo
799804
} as SiteTreeItem);
800805

@@ -813,7 +818,7 @@ describe('ActionsHubCommandHandlers', () => {
813818
status: WebsiteStatus.Active,
814819
websiteUrl: 'https://test-site.com',
815820
isCurrent: false,
816-
siteVisibility: Constants.SiteVisibility.PRIVATE
821+
siteVisibility: SiteVisibility.Private
817822
} as IWebsiteInfo
818823
} as SiteTreeItem);
819824

@@ -849,8 +854,10 @@ describe('ActionsHubCommandHandlers', () => {
849854
status: WebsiteStatus.Active,
850855
websiteUrl: 'https://test-site.com',
851856
isCurrent: false,
852-
siteVisibility: Constants.SiteVisibility.PUBLIC,
853-
siteManagementUrl: "https://inactive-site-1-management.com"
857+
siteVisibility: SiteVisibility.Public,
858+
siteManagementUrl: "https://inactive-site-1-management.com",
859+
createdOn: "2025-03-20",
860+
creator: "Test Creator"
854861
});
855862
mockShowInformationMessage.resolves(Constants.Strings.YES);
856863

@@ -870,8 +877,10 @@ describe('ActionsHubCommandHandlers', () => {
870877
status: WebsiteStatus.Active,
871878
websiteUrl: 'https://test-site.com',
872879
isCurrent: false,
873-
siteVisibility: Constants.SiteVisibility.PUBLIC,
874-
siteManagementUrl: "https://inactive-site-1-management.com"
880+
siteVisibility: SiteVisibility.Public,
881+
siteManagementUrl: "https://inactive-site-1-management.com",
882+
createdOn: "2025-03-20",
883+
creator: "Test Creator"
875884
});
876885
mockShowInformationMessage.resolves(undefined);
877886

@@ -889,8 +898,10 @@ describe('ActionsHubCommandHandlers', () => {
889898
status: WebsiteStatus.Active,
890899
websiteUrl: 'https://test-site.com',
891900
isCurrent: false,
892-
siteVisibility: Constants.SiteVisibility.PRIVATE,
893-
siteManagementUrl: "https://inactive-site-1-management.com"
901+
siteVisibility: SiteVisibility.Private,
902+
siteManagementUrl: "https://inactive-site-1-management.com",
903+
createdOn: "2025-03-20",
904+
creator: "Test Creator"
894905
});
895906

896907
await uploadSite(mockSiteTreeItem, "");
@@ -907,8 +918,10 @@ describe('ActionsHubCommandHandlers', () => {
907918
status: WebsiteStatus.Active,
908919
websiteUrl: 'https://test-site.com',
909920
isCurrent: false,
910-
siteVisibility: "PUBLIC", // Uppercase
911-
siteManagementUrl: "https://inactive-site-1-management.com"
921+
siteVisibility: SiteVisibility.Public,
922+
siteManagementUrl: "https://inactive-site-1-management.com",
923+
createdOn: "2025-03-20",
924+
creator: "Test Creator"
912925
});
913926
mockShowInformationMessage.resolves(Constants.Strings.YES);
914927

@@ -926,8 +939,10 @@ describe('ActionsHubCommandHandlers', () => {
926939
status: WebsiteStatus.Active,
927940
websiteUrl: 'https://test-site.com',
928941
isCurrent: false,
929-
siteVisibility: Constants.SiteVisibility.PRIVATE,
930-
siteManagementUrl: "https://inactive-site-1-management.com"
942+
siteVisibility: SiteVisibility.Private,
943+
siteManagementUrl: "https://inactive-site-1-management.com",
944+
createdOn: "2025-03-20",
945+
creator: "Test Creator"
931946
});
932947

933948
mockSendText.throws(new Error('Upload failed'));
@@ -1120,16 +1135,24 @@ describe('ActionsHubCommandHandlers', () => {
11201135
siteInfo: {
11211136
name: "Test Site",
11221137
websiteId: "test-id",
1123-
dataModelVersion: 1
1138+
dataModelVersion: 1,
1139+
websiteUrl: 'https://test-site.com',
1140+
siteVisibility: SiteVisibility.Public,
1141+
createdOn: "2025-03-20T00:00:00Z",
1142+
creator: "Test Creator"
11241143
} as IWebsiteInfo
11251144
} as SiteTreeItem);
11261145

11271146
expect(mockShowInformationMessage.calledOnce).to.be.true;
11281147

11291148
const message = mockShowInformationMessage.firstCall.args[1].detail;
11301149
expect(message).to.include("Friendly name: Test Site");
1131-
expect(message).to.include("Website ID: test-id");
1132-
expect(message).to.include("Data model version: v1");
1150+
expect(message).to.include("Website Id: test-id");
1151+
expect(message).to.include("Data model version: Standard");
1152+
expect(message).to.include("Website Url: https://test-site.com");
1153+
expect(message).to.include("Site visibility: Public");
1154+
expect(message).to.include("Creator: Test Creator");
1155+
expect(message).to.include("Created on: March 20, 2025");
11331156
});
11341157
});
11351158

0 commit comments

Comments
 (0)