Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d991bc0
Add support for blogs entity in web schema and utilities
Nov 4, 2025
0231168
Gate blog entity support behind ECS feature flag
Nov 6, 2025
f40ca52
Add support for ideas entity in schema and utilities
Nov 25, 2025
31fed49
Add support for Idea Forums entity in schema and utilities
Nov 25, 2025
800eb5d
Add support for lazy(conditional) folder creation for blogs, ideas, a…
Nov 25, 2025
933506d
Add support for Forum Announcements entity in schema and utilities
Nov 25, 2025
30f582d
Add support for Forum Posts entity and enhance lazy folder creation l…
Nov 25, 2025
e3d6e3b
Add support for blog posts and forum entities in schema and utilities
Nov 25, 2025
66d3c3f
Merge branch 'users/amitjoshi/supportBlogEditingInVSCodeWeb' of https…
Nov 25, 2025
0f6fc86
Add support for Forum Announcements and Forum Posts entities in porta…
Nov 26, 2025
68dbe7b
Refine multi-file fetch query parameters for blog and idea entities t…
Dec 10, 2025
2cb5efa
Enhance multi-file fetch query parameters for forum announcements to …
Dec 10, 2025
d4d1180
Merge branch 'main' of https://github.com/microsoft/powerplatform-vsc…
Dec 10, 2025
bee071c
Merge branch 'users/amitjoshi/supportForumInVscodeWeb' into users/ami…
Dec 10, 2025
2b510a6
Add tests for GetFileNameWithExtension and folderHelperUtility functions
Dec 10, 2025
55503a0
Add unit tests for GetFileNameWithExtension with various entity types
Dec 10, 2025
0e963a2
Merge branch 'users/amitjoshi/supportBlogEditingInVSCodeWeb' of https…
Dec 10, 2025
e192edc
Remove tests for folderHelperUtility and clean up remoteFetchProvider…
Dec 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/common/ecs-features/ecsFeatureGates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,13 @@ export const {
disallowedDuplicateFileHandlingOrgs: "",
}
});

export const {
feature: EnableBlogSupport
} = getFeatureConfigs({
teamName: PowerPagesClientName,
description: 'Enable blog file support in VSCode (web & desktop)',
fallback: {
enableBlogSupport: false,
}
});
24 changes: 21 additions & 3 deletions src/web/client/dal/remoteFetchProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import {
} from "../utilities/schemaHelperUtil";
import WebExtensionContext from "../WebExtensionContext";
import { webExtensionTelemetryEventNames } from "../../../common/OneDSLoggerTelemetry/web/client/webExtensionTelemetryEvents";
import { EntityMetadataKeyCore, SchemaEntityMetadata, folderExportType, schemaEntityKey, schemaEntityName, schemaKey, WEBPAGE_FOLDER_CONSTANTS } from "../schema/constants";
import { conditionalFolderEntities, EntityMetadataKeyCore, SchemaEntityMetadata, folderExportType, schemaEntityKey, schemaEntityName, schemaKey, WEBPAGE_FOLDER_CONSTANTS } from "../schema/constants";
import { getEntityNameForExpandedEntityContent, getRequestUrlForEntities } from "../utilities/folderHelperUtility";
import { IAttributePath, IFileInfo } from "../common/interfaces";
import { portal_schema_V2 } from "../schema/portalSchema";
import { ERROR_CONSTANTS } from "../../../common/ErrorConstants";
import { showErrorDialog } from "../../../common/utilities/errorHandlerUtil";
import { EnableServerLogicChanges, EnableDuplicateFileHandling } from "../../../common/ecs-features/ecsFeatureGates";
import { EnableServerLogicChanges, EnableDuplicateFileHandling, EnableBlogSupport } from "../../../common/ecs-features/ecsFeatureGates";
import { ECSFeaturesClient } from "../../../common/ecs-features/ecsFeatureClient";

export async function fetchDataFromDataverseAndUpdateVFS(
Expand Down Expand Up @@ -760,9 +760,27 @@ async function createVirtualFile(
entityMetadata
);

const fileUriParsed = vscode.Uri.parse(fileUri);

const { enableBlogSupport } = ECSFeaturesClient.getConfig(EnableBlogSupport);
// Ensure parent directory exists before writing file for conditional entities
// This enables lazy folder creation for blogs, ideas, ideaforums, forum announcements, and forum posts
if (enableBlogSupport && conditionalFolderEntities.includes(entityName as schemaEntityName)) {
const parentDirPath = fileUriParsed.path.substring(0, fileUriParsed.path.lastIndexOf('/'));
const parentDirUri = fileUriParsed.with({ path: parentDirPath });

try {
// createDirectory is idempotent - it checks if directory exists before creating
await portalsFS.createDirectory(parentDirUri);
} catch (error) {
//Directory might already exist or parent lookup might succeed anyway

}
}

// Call file system provider write call for buffering file data in VFS
await portalsFS.writeFile(
vscode.Uri.parse(fileUri),
fileUriParsed,
fileContent,
{ create: true, overwrite: true },
true
Expand Down
23 changes: 23 additions & 0 deletions src/web/client/schema/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export enum schemaEntityName {
BASICFORMS = "basicforms",
ADVANCEDFORMS = "advancedforms",
ADVANCEDFORMSTEPS = "advancedformsteps",
BLOGS = "blogs",
BLOGPOSTS = "blogposts",
IDEAS = "ideas",
IDEAFORUMS = "ideaforums",
FORUMANNOUNCEMENTS = "forumannouncements",
FORUMPOSTS = "forumposts",
}

export enum MultiFileSupportedEntityName {
Expand All @@ -64,6 +70,12 @@ export enum MultiFileSupportedEntityName {
LISTS = "lists",
BASICFORMS = "basicforms",
ADVANCEDFORMS = "advancedforms",
BLOGS = "blogs",
BLOGPOSTS = "blogposts",
IDEAS = "ideas",
IDEAFORUMS = "ideaforums",
FORUMANNOUNCEMENTS = "forumannouncements",
FORUMPOSTS = "forumposts",
}

// This decides the folder hierarchy a file being displayed in File explorer will follow.
Expand Down Expand Up @@ -100,3 +112,14 @@ export const WEBPAGE_FOLDER_CONSTANTS = {
export function getRootWebPageIdForTelemetry(rootWebPageId: string | undefined | null): string {
return rootWebPageId || WEBPAGE_FOLDER_CONSTANTS.NULL_PLACEHOLDER;
}

// Entities that should only have folders created when they contain data
// These folders will be created lazily when files are fetched
export const conditionalFolderEntities = [
schemaEntityName.BLOGS,
schemaEntityName.BLOGPOSTS,
schemaEntityName.IDEAS,
schemaEntityName.IDEAFORUMS,
schemaEntityName.FORUMANNOUNCEMENTS,
schemaEntityName.FORUMPOSTS,
];
221 changes: 221 additions & 0 deletions src/web/client/schema/portalSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { ECSFeaturesClient } from "../../../common/ecs-features/ecsFeatureClient";
import { EnableBlogSupport } from "../../../common/ecs-features/ecsFeatureGates";

export const portal_schema_V1 = {
entities: {
dataSourceProperties: {
Expand Down Expand Up @@ -232,6 +235,114 @@ export const portal_schema_V1 = {
_attributes: "adx_registerstartupscript",
_attributesExtension: new Map([["adx_registerstartupscript", "advancedformstep.customjs.js"]]),
},
...(ECSFeaturesClient.getConfig(EnableBlogSupport).enableBlogSupport ? [
{
relationships: "",
_vscodeentityname: "blogs",
_dataverseenityname: "adx_blogs",
_displayname: "Blog",
_etc: "10061",
_primaryidfield: "adx_blogid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "blogs",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_blogid eq {entityId}&$select=adx_name,adx_summary",
_multiFileFetchQueryParameters:
"?$filter=_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_summary,adx_blogid&$count=true",
_attributes: "adx_summary",
_attributesExtension: new Map([["adx_summary", "html"]]),
},
{
relationships: "",
_vscodeentityname: "ideas",
_dataverseenityname: "adx_ideas",
_displayname: "Idea",
_etc: "10062",
_primaryidfield: "adx_ideaid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "ideas",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_ideaid eq {entityId}&$select=adx_name,adx_copy",
_multiFileFetchQueryParameters:
"?$filter=_adx_ideaforumid_value ne null and adx_ideaforumId/_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_copy,adx_ideaid&$count=true",
_attributes: "adx_copy",
_attributesExtension: new Map([["adx_copy", "html"]]),
},
{
relationships: "", _vscodeentityname: "blogposts",
_dataverseenityname: "adx_blogposts",
_displayname: "Blog Post",
_etc: "10056",
_primaryidfield: "adx_blogpostid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "blog-posts",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_blogpostid eq {entityId}&$select=adx_name,adx_copy",
_multiFileFetchQueryParameters:
"?$filter=_adx_blogid_value ne null and adx_blogid/_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_copy,adx_blogpostid&$count=true",
_attributes: "adx_copy",
_attributesExtension: new Map([["adx_copy", "html"]]),
},
{
relationships: "", _vscodeentityname: "ideaforums",
_dataverseenityname: "adx_ideaforums",
_displayname: "Idea Forum",
_etc: "10063",
_primaryidfield: "adx_ideaforumid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "idea-forums",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_ideaforumid eq {entityId}&$select=adx_name,adx_summary",
_multiFileFetchQueryParameters:
"?$filter=_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_summary,adx_ideaforumid&$count=true",
_attributes: "adx_summary",
_attributesExtension: new Map([["adx_summary", "html"]]),
},
{
relationships: "",
_vscodeentityname: "forumannouncements",
_dataverseenityname: "adx_communityforumannouncements",
_displayname: "Forum Announcement",
_etc: "10064",
_primaryidfield: "adx_communityforumannouncementid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "forum-announcements",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_communityforumannouncementid eq {entityId}&$select=adx_name,adx_content",
_multiFileFetchQueryParameters:
"?$filter=_adx_forumid_value ne null and adx_forumid/_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_content,adx_communityforumannouncementid&$count=true",
_attributes: "adx_content",
_attributesExtension: new Map([["adx_content", "html"]]),
},
{
relationships: "",
_vscodeentityname: "forumposts",
_dataverseenityname: "adx_communityforumposts",
_displayname: "Forum Post",
_etc: "10065",
_primaryidfield: "adx_communityforumpostid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "forum-posts",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_communityforumpostid eq {entityId}&$select=adx_name,adx_content",
_multiFileFetchQueryParameters:
"?$filter=_adx_forumthreadid_value ne null &$select=adx_name,adx_content,adx_communityforumpostid&$count=true",
_attributes: "adx_content",
_attributesExtension: new Map([["adx_content", "html"]]),
},
] : []),
],
"_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
},
Expand Down Expand Up @@ -470,6 +581,116 @@ export const portal_schema_V2 = {
_attributes: "content.registerstartupscript",
_attributesExtension: new Map([["content.registerstartupscript", "advancedformstep.customjs.js"]]),
},
...(ECSFeaturesClient.getConfig(EnableBlogSupport).enableBlogSupport ? [
{
relationships: "",
_vscodeentityname: "blogs",
_dataverseenityname: "adx_blogs",
_displayname: "Blog",
_etc: "10061",
_primaryidfield: "adx_blogid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "blogs",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_blogid eq {entityId}&$select=adx_name,adx_summary",
_multiFileFetchQueryParameters:
"?$filter=_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_summary,adx_blogid&$count=true",
_attributes: "adx_summary",
_attributesExtension: new Map([["adx_summary", "html"]]),
},
{
relationships: "",
_vscodeentityname: "blogposts",
_dataverseenityname: "adx_blogposts",
_displayname: "Blog Post",
_etc: "10056",
_primaryidfield: "adx_blogpostid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "blog-posts",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_blogpostid eq {entityId}&$select=adx_name,adx_copy",
_multiFileFetchQueryParameters:
"?$filter=_adx_blogid_value ne null and adx_blogid/_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_copy,adx_blogpostid&$count=true",
_attributes: "adx_copy",
_attributesExtension: new Map([["adx_copy", "html"]]),
},
{
relationships: "",
_vscodeentityname: "ideas",
_dataverseenityname: "adx_ideas",
_displayname: "Idea",
_etc: "10062",
_primaryidfield: "adx_ideaid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "ideas",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_ideaid eq {entityId}&$select=adx_name,adx_copy",
_multiFileFetchQueryParameters:
"?$filter=_adx_ideaforumid_value ne null and adx_ideaforumId/_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_copy,adx_ideaid&$count=true",
_attributes: "adx_copy",
_attributesExtension: new Map([["adx_copy", "html"]]),
},
{
relationships: "",
_vscodeentityname: "ideaforums",
_dataverseenityname: "adx_ideaforums",
_displayname: "Idea Forum",
_etc: "10063",
_primaryidfield: "adx_ideaforumid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "idea-forums",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_ideaforumid eq {entityId}&$select=adx_name,adx_summary",
_multiFileFetchQueryParameters:
"?$filter=_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_summary,adx_ideaforumid&$count=true",
_attributes: "adx_summary",
_attributesExtension: new Map([["adx_summary", "html"]]),
},
{
relationships: "",
_vscodeentityname: "forumannouncements",
_dataverseenityname: "adx_communityforumannouncements",
_displayname: "Forum Announcement",
_etc: "10064",
_primaryidfield: "adx_communityforumannouncementid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "forum-announcements",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_communityforumannouncementid eq {entityId}&$select=adx_name,adx_content",
_multiFileFetchQueryParameters:
"?$filter=_adx_forumid_value ne null and adx_forumid/_adx_websiteid_value eq {websiteId} &$select=adx_name,adx_content,adx_communityforumannouncementid&$count=true",
_attributes: "adx_content",
_attributesExtension: new Map([["adx_content", "html"]]),
},
{
relationships: "",
_vscodeentityname: "forumposts",
_dataverseenityname: "adx_communityforumposts",
_displayname: "Forum Post",
_etc: "10065",
_primaryidfield: "adx_communityforumpostid",
_primarynamefield: "adx_name",
_disableplugins: "true",
_foldername: "forum-posts",
_exporttype: "SingleFolder",
_fetchQueryParameters:
"?$filter=adx_communityforumpostid eq {entityId}&$select=adx_name,adx_content",
_multiFileFetchQueryParameters:
"?$filter=_adx_forumthreadid_value ne null &$select=adx_name,adx_content,adx_communityforumpostid&$count=true",
_attributes: "adx_content",
_attributesExtension: new Map([["adx_content", "html"]]),
},
] : []),
],
},
};
Loading