Skip to content

Commit c309102

Browse files
authored
Merge branch 'main' into users/amitjoshi/changeLogUpdate2.0.110
2 parents cb3e895 + a629ac2 commit c309102

37 files changed

+3619
-2958
lines changed

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

Lines changed: 0 additions & 1186 deletions
This file was deleted.

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ import { oneDSLoggerWrapper } from "../../../common/OneDSLoggerTelemetry/oneDSLo
1212
import { EnvironmentGroupTreeItem } from "./tree-items/EnvironmentGroupTreeItem";
1313
import { IEnvironmentInfo } from "./models/IEnvironmentInfo";
1414
import { PacTerminal } from "../../lib/PacTerminal";
15-
import { fetchWebsites, openActiveSitesInStudio, openInactiveSitesInStudio, previewSite, createNewAuthProfile, refreshEnvironment, showEnvironmentDetails, switchEnvironment, revealInOS, openSiteManagement, uploadSite, showSiteDetails, downloadSite, openInStudio, reactivateSite, runCodeQLScreening, loginToMatch } from "./ActionsHubCommandHandlers";
15+
import { runCodeQLScreening } from "./handlers/code-ql/RunCodeQlScreeningHandler";
16+
import { revealInOS } from "./handlers/RevealInOSHandler";
17+
import { createNewAuthProfile } from "./handlers/CreateNewAuthProfileHandler";
18+
import { previewSite } from "./handlers/PreviewSiteHandler";
19+
import { openActiveSitesInStudio, openInactiveSitesInStudio, openSiteInStudio } from "./handlers/OpenSiteInStudioHandler";
20+
import { switchEnvironment } from "./handlers/SwitchEnvironmentHandler";
21+
import { showEnvironmentDetails } from "./handlers/ShowEnvironmentDetailsHandler";
22+
import { refreshEnvironment } from "./handlers/RefreshEnvironmentHandler";
1623
import PacContext from "../../pac/PacContext";
1724
import CurrentSiteContext from "./CurrentSiteContext";
1825
import { IOtherSiteInfo, IWebsiteDetails } from "../../../common/services/Interfaces";
@@ -21,6 +28,13 @@ import { getBaseEventInfo } from "./TelemetryHelper";
2128
import { PROVIDER_ID } from "../../../common/services/Constants";
2229
import { getOIDFromToken } from "../../../common/services/AuthenticationProvider";
2330
import ArtemisContext from "../../ArtemisContext";
31+
import { fetchWebsites } from "./ActionsHubUtils";
32+
import { openSiteManagement } from "./handlers/OpenSiteManagementHandler";
33+
import { reactivateSite } from "./handlers/ReactivateSiteHandler";
34+
import { uploadSite } from "./handlers/UploadSiteHandler";
35+
import { showSiteDetails } from "./handlers/ShowSiteDetailsHandler";
36+
import { downloadSite } from "./handlers/DownloadSiteHandler";
37+
import { loginToMatch } from "./handlers/LoginToMatchHandler";
2438

2539
export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider<ActionsHubTreeItem> {
2640
private readonly _disposables: vscode.Disposable[] = [];
@@ -87,7 +101,7 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider<Actio
87101

88102
private async checkAuthInfo(): Promise<boolean> {
89103
const authInfo = PacContext.AuthInfo;
90-
const session = await vscode.authentication.getSession(PROVIDER_ID, [], { silent: true });
104+
const session = await vscode.authentication.getSession(PROVIDER_ID, [], { silent: true });
91105

92106
if (session && session.accessToken && authInfo && authInfo.OrganizationFriendlyName) {
93107
return true;
@@ -175,7 +189,7 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider<Actio
175189

176190
try {
177191
const authInfo = PacContext.AuthInfo;
178-
if (await this.checkAuthInfo() === true ) {
192+
if (await this.checkAuthInfo() === true) {
179193
// Check if accounts match before loading websites
180194
const accountsMatch = await this.checkAccountsMatch();
181195
if (!accountsMatch) {
@@ -195,7 +209,7 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider<Actio
195209
currentEnvironmentName: authInfo!.OrganizationFriendlyName //Already checked in checkAuthInfo
196210
};
197211

198-
if(!this._otherSites.length){
212+
if (!this._otherSites.length) {
199213
return [new EnvironmentGroupTreeItem(currentEnvInfo, this._context, this._activeSites, this._inactiveSites)];
200214
}
201215
return [
@@ -248,7 +262,7 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider<Actio
248262

249263
vscode.commands.registerCommand("microsoft.powerplatform.pages.actionsHub.activeSite.downloadSite", downloadSite),
250264

251-
vscode.commands.registerCommand("microsoft.powerplatform.pages.actionsHub.activeSite.openInStudio", openInStudio),
265+
vscode.commands.registerCommand("microsoft.powerplatform.pages.actionsHub.activeSite.openInStudio", openSiteInStudio),
252266

253267
vscode.commands.registerCommand("microsoft.powerplatform.pages.actionsHub.inactiveSite.reactivateSite", reactivateSite),
254268

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

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

6+
import * as vscode from "vscode";
7+
import * as fs from "fs";
8+
import * as yaml from "yaml";
69
import path from "path";
710
import os from "os";
11+
import { traceError, traceInfo } from "./TelemetryHelper";
12+
import { Constants } from "./Constants";
13+
import { IOtherSiteInfo, IWebsiteDetails, WebsiteYaml } from "../../../common/services/Interfaces";
14+
import PacContext from "../../pac/PacContext";
15+
import ArtemisContext from "../../ArtemisContext";
16+
import { getActiveWebsites, getAllWebsites } from "../../../common/utilities/WebsiteUtil";
17+
import { ServiceEndpointCategory } from "../../../common/services/Constants";
18+
import { getWebsiteRecordId, getWebsiteYamlPath, hasWebsiteYaml } from "../../../common/utilities/WorkspaceInfoFinderUtil";
19+
import { POWERPAGES_SITE_FOLDER, UTF8_ENCODING } from "../../../common/constants";
820

21+
export function createKnownSiteIdsSet(
22+
activeSites: IWebsiteDetails[] | undefined,
23+
inactiveSites: IWebsiteDetails[] | undefined
24+
): Set<string> {
25+
const knownSiteIds = new Set<string>();
26+
27+
activeSites?.forEach(site => {
28+
if (site.websiteRecordId) {
29+
knownSiteIds.add(site.websiteRecordId.toLowerCase());
30+
}
31+
});
32+
33+
inactiveSites?.forEach(site => {
34+
if (site.websiteRecordId) {
35+
knownSiteIds.add(site.websiteRecordId.toLowerCase());
36+
}
37+
});
38+
39+
return knownSiteIds;
40+
}
41+
42+
/**
43+
* Finds Power Pages sites in the parent folder that aren't in the known sites list
44+
* @param knownSiteIds Set of site IDs that should be excluded from results
45+
* @returns Array of site information objects for sites found in the parent folder
46+
*/
47+
export function findOtherSites(knownSiteIds: Set<string>, fsModule = fs, yamlModule = yaml): IOtherSiteInfo[] {
48+
traceInfo(Constants.EventNames.ACTIONS_HUB_FIND_OTHER_SITES_CALLED, { methodName: findOtherSites.name });
49+
50+
// Get the workspace folders
51+
const workspaceFolders = vscode.workspace.workspaceFolders;
52+
if (!workspaceFolders || workspaceFolders.length === 0) {
53+
return [];
54+
}
55+
56+
const currentWorkspaceFolder = workspaceFolders[0].uri.fsPath;
57+
const parentFolder = path.dirname(currentWorkspaceFolder);
58+
59+
try {
60+
// Get directories in the parent folder
61+
const items = fsModule.readdirSync(parentFolder, { withFileTypes: true });
62+
const directories = items
63+
.filter(item => item.isDirectory())
64+
.map(item => path.join(parentFolder, item.name));
65+
66+
// Make sure we include the current workspace folder
67+
if (!directories.includes(currentWorkspaceFolder)) {
68+
directories.push(currentWorkspaceFolder);
69+
}
70+
71+
// Check each directory for website.yml or .powerpages-site folder
72+
const otherSites: IOtherSiteInfo[] = [];
73+
for (const dir of directories) {
74+
let websiteYamlPath = getWebsiteYamlPath(dir);
75+
let hasWebsiteYamlFile = hasWebsiteYaml(dir);
76+
const powerPagesSiteFolderExists = fs.existsSync(dir)
77+
let workingDir = dir;
78+
79+
if (powerPagesSiteFolderExists) {
80+
workingDir = path.join(dir, POWERPAGES_SITE_FOLDER);
81+
websiteYamlPath = getWebsiteYamlPath(workingDir);
82+
hasWebsiteYamlFile = hasWebsiteYaml(workingDir);
83+
}
84+
85+
if (hasWebsiteYamlFile) {
86+
try {
87+
// Use the utility function to get website record ID
88+
const websiteId = getWebsiteRecordId(workingDir);
89+
90+
// Only include sites that aren't already in active or inactive sites
91+
if (websiteId && !knownSiteIds.has(websiteId.toLowerCase())) {
92+
// Parse website.yml to get site details for the name
93+
const yamlContent = fsModule.readFileSync(websiteYamlPath, UTF8_ENCODING);
94+
const websiteData = yamlModule.parse(yamlContent) as WebsiteYaml;
95+
96+
otherSites.push({
97+
name: websiteData?.adx_name || websiteData?.name || path.basename(dir), // Use folder name as fallback
98+
websiteId: websiteId,
99+
folderPath: dir,
100+
isCodeSite: powerPagesSiteFolderExists
101+
});
102+
}
103+
} catch (error) {
104+
traceError(
105+
Constants.EventNames.ACTIONS_HUB_FIND_OTHER_SITES_YAML_PARSE_FAILED,
106+
error as Error,
107+
{ methodName: findOtherSites.name }
108+
);
109+
}
110+
}
111+
}
112+
113+
return otherSites;
114+
} catch (error) {
115+
traceError(
116+
Constants.EventNames.ACTIONS_HUB_FIND_OTHER_SITES_FAILED,
117+
error as Error,
118+
{ methodName: findOtherSites.name }
119+
);
120+
return [];
121+
}
122+
}
9123

10124
export const getDefaultCodeQLDatabasePath = (): string => {
11125
// Use a temporary directory for the CodeQL database
@@ -14,3 +128,75 @@ export const getDefaultCodeQLDatabasePath = (): string => {
14128
return path.join(tempDir, dbName);
15129
};
16130

131+
const sortByCreatedOn = <T extends { createdOn?: string | null }>(item1: T, item2: T): number => {
132+
const date1 = new Date(item1.createdOn || '').valueOf(); //NaN if createdOn is null or undefined
133+
const date2 = new Date(item2.createdOn || '').valueOf();
134+
return date2 - date1; // Sort in descending order (newest first)
135+
}
136+
137+
export const fetchWebsites = async (): Promise<{ activeSites: IWebsiteDetails[], inactiveSites: IWebsiteDetails[], otherSites: IOtherSiteInfo[] }> => {
138+
traceInfo(Constants.EventNames.ACTIONS_HUB_FETCH_WEBSITES_CALLED, { methodName: fetchWebsites.name });
139+
try {
140+
const orgInfo = PacContext.OrgInfo;
141+
if (ArtemisContext.ServiceResponse?.stamp && orgInfo) {
142+
let allSites: IWebsiteDetails[] = [];
143+
let activeWebsiteDetails: IWebsiteDetails[] = [];
144+
[activeWebsiteDetails, allSites] = await Promise.all([
145+
getActiveWebsites(ArtemisContext.ServiceResponse?.stamp, orgInfo.EnvironmentId),
146+
getAllWebsites(orgInfo)
147+
]);
148+
const activeSiteIds = new Set(activeWebsiteDetails.map(activeSite => activeSite.websiteRecordId));
149+
const inactiveWebsiteDetails = allSites?.filter(site => !activeSiteIds.has(site.websiteRecordId)) || [];
150+
activeWebsiteDetails = activeWebsiteDetails.map(detail => {
151+
const site = allSites.find(site => site.websiteRecordId === detail.websiteRecordId);
152+
153+
if (!site) {
154+
return detail;
155+
}
156+
157+
return {
158+
...detail,
159+
siteManagementUrl: site.siteManagementUrl,
160+
isCodeSite: site.isCodeSite,
161+
createdOn: site.createdOn,
162+
creator: site.creator,
163+
}
164+
});
165+
166+
activeWebsiteDetails.sort(sortByCreatedOn);
167+
inactiveWebsiteDetails.sort(sortByCreatedOn);
168+
169+
const currentEnvSiteIds = createKnownSiteIdsSet(activeWebsiteDetails, inactiveWebsiteDetails);
170+
const otherSites = findOtherSites(currentEnvSiteIds);
171+
172+
return { activeSites: activeWebsiteDetails, inactiveSites: inactiveWebsiteDetails, otherSites: otherSites };
173+
}
174+
} catch (error) {
175+
traceError(Constants.EventNames.ACTIONS_HUB_FETCH_WEBSITES_FAILED, error as Error, { methodName: fetchWebsites.name });
176+
}
177+
178+
return { activeSites: [], inactiveSites: [], otherSites: [] };
179+
}
180+
181+
export const getStudioBaseUrl = (): string => {
182+
const artemisContext = ArtemisContext.ServiceResponse;
183+
184+
switch (artemisContext.stamp) {
185+
case ServiceEndpointCategory.TEST:
186+
return Constants.StudioEndpoints.TEST;
187+
case ServiceEndpointCategory.PREPROD:
188+
return Constants.StudioEndpoints.TEST; //Studio for preprod is same as test
189+
case ServiceEndpointCategory.PROD:
190+
return Constants.StudioEndpoints.PROD;
191+
case ServiceEndpointCategory.DOD:
192+
return Constants.StudioEndpoints.DOD;
193+
case ServiceEndpointCategory.GCC:
194+
return Constants.StudioEndpoints.GCC;
195+
case ServiceEndpointCategory.HIGH:
196+
return Constants.StudioEndpoints.HIGH;
197+
case ServiceEndpointCategory.MOONCAKE:
198+
return Constants.StudioEndpoints.MOONCAKE;
199+
}
200+
201+
return "";
202+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ export const Constants = {
254254
ACTIONS_HUB_ACCOUNT_CHECK_FAILED: "ActionsHubAccountCheckFailed",
255255
ACTIONS_HUB_ACCOUNT_MISMATCH_UI_SHOWN: "ActionsHubAccountMismatchUIShown",
256256
ACTIONS_HUB_ACCOUNT_MATCH_RESOLVED: "ActionsHubAccountMatchResolved",
257-
ACTIONS_HUB_LOGIN_PROMPT_CLICKED: "ActionsHubLoginPromptClicked"
257+
ACTIONS_HUB_LOGIN_PROMPT_CLICKED: "ActionsHubLoginPromptClicked",
258+
ACTIONS_HUB_REACTIVATE_SITE_CALLED: "ActionsHubReactivateSiteCalled"
258259
},
259260
FeatureNames: {
260261
REFRESH_ENVIRONMENT: "RefreshEnvironment"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
import { SUCCESS } from "../../../../common/constants";
7+
import { authenticateUserInVSCode } from "../../../../common/services/AuthenticationProvider";
8+
import { createAuthProfileExp } from "../../../../common/utilities/PacAuthUtil";
9+
import PacContext from "../../../pac/PacContext";
10+
import { PacWrapper } from "../../../pac/PacWrapper";
11+
import { Constants } from "../Constants";
12+
import { traceError, traceInfo } from "../TelemetryHelper";
13+
14+
export const createNewAuthProfile = async (pacWrapper: PacWrapper): Promise<void> => {
15+
traceInfo(Constants.EventNames.ACTIONS_HUB_CREATE_AUTH_PROFILE_CALLED, { methodName: createNewAuthProfile.name });
16+
try {
17+
const orgUrl = PacContext.OrgInfo?.OrgUrl ?? '';
18+
19+
// if orgUrl is present then directly authenticate in VS Code
20+
if (orgUrl) {
21+
await authenticateUserInVSCode();
22+
return;
23+
}
24+
25+
const pacAuthCreateOutput = await createAuthProfileExp(pacWrapper);
26+
if (pacAuthCreateOutput && pacAuthCreateOutput.Status === SUCCESS) {
27+
const results = pacAuthCreateOutput.Results;
28+
if (Array.isArray(results) && results.length > 0) {
29+
const orgUrl = results[0].ActiveOrganization?.Item2;
30+
if (orgUrl) {
31+
await authenticateUserInVSCode();
32+
} else {
33+
traceError(
34+
createNewAuthProfile.name,
35+
new Error(Constants.Strings.ORGANIZATION_URL_MISSING),
36+
{ methodName: createNewAuthProfile.name }
37+
);
38+
}
39+
} else {
40+
traceError(
41+
createNewAuthProfile.name,
42+
new Error(Constants.Strings.EMPTY_RESULTS_ARRAY),
43+
{ methodName: createNewAuthProfile.name }
44+
);
45+
}
46+
} else {
47+
traceError(
48+
createNewAuthProfile.name,
49+
new Error(Constants.Strings.PAC_AUTH_OUTPUT_FAILURE),
50+
{ methodName: createNewAuthProfile.name }
51+
);
52+
}
53+
} catch (error) {
54+
traceError(
55+
Constants.EventNames.ACTIONS_HUB_CREATE_AUTH_PROFILE_FAILED,
56+
error as Error,
57+
{ methodName: createNewAuthProfile.name }
58+
);
59+
}
60+
};

0 commit comments

Comments
 (0)