diff --git a/contact-center/README.md b/contact-center/README.md index d8e23fdf..0ff95b51 100644 --- a/contact-center/README.md +++ b/contact-center/README.md @@ -13,7 +13,7 @@ Integrate Copilot Studio agents with contact center platforms and live agent han | Folder | Description | |--------|-------------| -| [Skill Handoff](./servicenow/HandoverToLiveAgentUsingSkill/) | Skill-based handoff to live agents using M365 Agents SDK | -| [Salesforce](./servicenow/Salesforce/) | Salesforce Einstein Bot integration via DirectLine API | -| [ServiceNow](./servicenow/ServiceNow/) | ServiceNow Virtual Agent integration via DirectLine | +| [Skill Handoff](./skill-handoff/) | Skill-based handoff to live agents using M365 Agents SDK | +| [Salesforce](./salesforce/) | Salesforce Einstein Bot integration via DirectLine API | +| [ServiceNow](./servicenow/) | ServiceNow Virtual Agent integration via DirectLine | | [Genesys Handoff](./genesys-handoff/) | Live agent handoff to Genesys (.NET) — *M365 Agents SDK repo* | diff --git a/contact-center/servicenow/Salesforce/ApexClasses/DL_GetActivity.cls b/contact-center/salesforce/ApexClasses/DL_GetActivity.cls similarity index 100% rename from contact-center/servicenow/Salesforce/ApexClasses/DL_GetActivity.cls rename to contact-center/salesforce/ApexClasses/DL_GetActivity.cls diff --git a/contact-center/servicenow/Salesforce/ApexClasses/DL_GetConversation.cls b/contact-center/salesforce/ApexClasses/DL_GetConversation.cls similarity index 100% rename from contact-center/servicenow/Salesforce/ApexClasses/DL_GetConversation.cls rename to contact-center/salesforce/ApexClasses/DL_GetConversation.cls diff --git a/contact-center/servicenow/Salesforce/ApexClasses/DL_PostActivity.cls b/contact-center/salesforce/ApexClasses/DL_PostActivity.cls similarity index 100% rename from contact-center/servicenow/Salesforce/ApexClasses/DL_PostActivity.cls rename to contact-center/salesforce/ApexClasses/DL_PostActivity.cls diff --git a/contact-center/servicenow/Salesforce/Metadata/classes/DL_GetActivity.cls-meta.xml b/contact-center/salesforce/Metadata/classes/DL_GetActivity.cls-meta.xml similarity index 100% rename from contact-center/servicenow/Salesforce/Metadata/classes/DL_GetActivity.cls-meta.xml rename to contact-center/salesforce/Metadata/classes/DL_GetActivity.cls-meta.xml diff --git a/contact-center/servicenow/Salesforce/Metadata/classes/DL_GetConversation.cls-meta.xml b/contact-center/salesforce/Metadata/classes/DL_GetConversation.cls-meta.xml similarity index 100% rename from contact-center/servicenow/Salesforce/Metadata/classes/DL_GetConversation.cls-meta.xml rename to contact-center/salesforce/Metadata/classes/DL_GetConversation.cls-meta.xml diff --git a/contact-center/servicenow/Salesforce/Metadata/classes/DL_PostActivity.cls-meta.xml b/contact-center/salesforce/Metadata/classes/DL_PostActivity.cls-meta.xml similarity index 100% rename from contact-center/servicenow/Salesforce/Metadata/classes/DL_PostActivity.cls-meta.xml rename to contact-center/salesforce/Metadata/classes/DL_PostActivity.cls-meta.xml diff --git a/contact-center/servicenow/Salesforce/Metadata/externalCredentials/Directline.externalCredential-meta.xml b/contact-center/salesforce/Metadata/externalCredentials/Directline.externalCredential-meta.xml similarity index 100% rename from contact-center/servicenow/Salesforce/Metadata/externalCredentials/Directline.externalCredential-meta.xml rename to contact-center/salesforce/Metadata/externalCredentials/Directline.externalCredential-meta.xml diff --git a/contact-center/servicenow/Salesforce/Metadata/namedCredentials/Directline.namedCredential-meta.xml b/contact-center/salesforce/Metadata/namedCredentials/Directline.namedCredential-meta.xml similarity index 100% rename from contact-center/servicenow/Salesforce/Metadata/namedCredentials/Directline.namedCredential-meta.xml rename to contact-center/salesforce/Metadata/namedCredentials/Directline.namedCredential-meta.xml diff --git a/contact-center/servicenow/Salesforce/Metadata/remoteSiteSettings/DirectLine.remoteSite-meta.xml b/contact-center/salesforce/Metadata/remoteSiteSettings/DirectLine.remoteSite-meta.xml similarity index 100% rename from contact-center/servicenow/Salesforce/Metadata/remoteSiteSettings/DirectLine.remoteSite-meta.xml rename to contact-center/salesforce/Metadata/remoteSiteSettings/DirectLine.remoteSite-meta.xml diff --git a/contact-center/servicenow/Salesforce/README.md b/contact-center/salesforce/README.md similarity index 100% rename from contact-center/servicenow/Salesforce/README.md rename to contact-center/salesforce/README.md diff --git a/contact-center/servicenow/Salesforce/scripts/deploy.ps1 b/contact-center/salesforce/scripts/deploy.ps1 similarity index 100% rename from contact-center/servicenow/Salesforce/scripts/deploy.ps1 rename to contact-center/salesforce/scripts/deploy.ps1 diff --git a/contact-center/servicenow/Salesforce/scripts/deploy.sh b/contact-center/salesforce/scripts/deploy.sh similarity index 100% rename from contact-center/servicenow/Salesforce/scripts/deploy.sh rename to contact-center/salesforce/scripts/deploy.sh diff --git a/contact-center/servicenow/Salesforce/scripts/grant-bot-permissions.ps1 b/contact-center/salesforce/scripts/grant-bot-permissions.ps1 similarity index 100% rename from contact-center/servicenow/Salesforce/scripts/grant-bot-permissions.ps1 rename to contact-center/salesforce/scripts/grant-bot-permissions.ps1 diff --git a/contact-center/servicenow/Salesforce/scripts/grant-bot-permissions.sh b/contact-center/salesforce/scripts/grant-bot-permissions.sh similarity index 100% rename from contact-center/servicenow/Salesforce/scripts/grant-bot-permissions.sh rename to contact-center/salesforce/scripts/grant-bot-permissions.sh diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.funcignore b/contact-center/servicenow/DirectLineAzureFunction/.funcignore similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.funcignore rename to contact-center/servicenow/DirectLineAzureFunction/.funcignore diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.gitignore b/contact-center/servicenow/DirectLineAzureFunction/.gitignore similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.gitignore rename to contact-center/servicenow/DirectLineAzureFunction/.gitignore diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/extensions.json b/contact-center/servicenow/DirectLineAzureFunction/.vscode/extensions.json similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/extensions.json rename to contact-center/servicenow/DirectLineAzureFunction/.vscode/extensions.json diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/launch.json b/contact-center/servicenow/DirectLineAzureFunction/.vscode/launch.json similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/launch.json rename to contact-center/servicenow/DirectLineAzureFunction/.vscode/launch.json diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/settings.json b/contact-center/servicenow/DirectLineAzureFunction/.vscode/settings.json similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/settings.json rename to contact-center/servicenow/DirectLineAzureFunction/.vscode/settings.json diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/tasks.json b/contact-center/servicenow/DirectLineAzureFunction/.vscode/tasks.json similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/.vscode/tasks.json rename to contact-center/servicenow/DirectLineAzureFunction/.vscode/tasks.json diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/host.json b/contact-center/servicenow/DirectLineAzureFunction/host.json similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/host.json rename to contact-center/servicenow/DirectLineAzureFunction/host.json diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/package.json b/contact-center/servicenow/DirectLineAzureFunction/package.json similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/package.json rename to contact-center/servicenow/DirectLineAzureFunction/package.json diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/src/functions/relayToDirectLine.js b/contact-center/servicenow/DirectLineAzureFunction/src/functions/relayToDirectLine.js similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/src/functions/relayToDirectLine.js rename to contact-center/servicenow/DirectLineAzureFunction/src/functions/relayToDirectLine.js diff --git a/contact-center/servicenow/ServiceNow/DirectLineAzureFunction/src/index.js b/contact-center/servicenow/DirectLineAzureFunction/src/index.js similarity index 100% rename from contact-center/servicenow/ServiceNow/DirectLineAzureFunction/src/index.js rename to contact-center/servicenow/DirectLineAzureFunction/src/index.js diff --git a/contact-center/servicenow/README.md b/contact-center/servicenow/README.md index dbd64973..ea33f2e6 100644 --- a/contact-center/servicenow/README.md +++ b/contact-center/servicenow/README.md @@ -1,15 +1,17 @@ --- -nav_exclude: true -search_exclude: true +title: ServiceNow +parent: Contact Center +nav_order: 3 --- -# Engagement Hub Integrations +# Copilot Studio - ServiceNow Integration Samples -Integrate Copilot Studio agents with engagement hub platforms including ServiceNow and Salesforce. +This folder contains sample code for integrating Microsoft Copilot Studio with ServiceNow Virtual Agent, enabling seamless handoff from virtual agent to live agent. -## Contents +## Assets Included -| Folder | Description | -|--------|-------------| -| [HandoverToLiveAgentUsingSkill/](./HandoverToLiveAgentUsingSkill/) | Skill-based handoff to live agents | -| [Salesforce/](./Salesforce/) | Salesforce Einstein Bot integration via DirectLine API | -| [ServiceNow/](./ServiceNow/) | ServiceNow integration via DirectLine | +| Asset | Description | File | +|-------|-------------|------| +| **Azure Function** | Relay service that bridges ServiceNow with the Direct Line API | [`DirectLineAzureFunction/relayToDirectLine`](./DirectLineAzureFunction/) | +| **ServiceNow Script Include** | Custom transformer that detects `handoff.initiate` events and triggers agent escalation | [`ScriptIncludes/CustomDirectLineInboundTransformer.js`](./ScriptIncludes/CustomDirectLineInboundTransformer.js) | + +These samples are companion code for the official documentation at: [Microsoft Learn - Copilot Studio with ServiceNow](https://learn.microsoft.com/en-us/microsoft-copilot-studio/customer-copilot-servicenow) diff --git a/contact-center/servicenow/Salesforce/CLAUDE.md b/contact-center/servicenow/Salesforce/CLAUDE.md deleted file mode 100644 index 7be3ca96..00000000 --- a/contact-center/servicenow/Salesforce/CLAUDE.md +++ /dev/null @@ -1,162 +0,0 @@ -# Salesforce Integration - Copilot Studio - -## Project Overview - -This project contains Apex classes and deployment scripts for integrating Microsoft Copilot Studio with Salesforce Einstein Bots via the DirectLine API. - -**Related repositories:** -- **Samples**: `/Users/administrator/projects/CopilotStudioSamples/IntegrateWithEngagementHub/Salesforce/` -- **Documentation**: `/Users/administrator/projects/docs/businessapps-copilot-docs-pr/copilot-studio/customer-copilot-salesforce-handoff.md` - ---- - -## Important Notes - -### Agentforce Support Removed (February 2026) - -Agentforce integration code was removed from the repo because there is no reliable integration path at this time. The research findings below are preserved as institutional knowledge for if/when Agentforce integration becomes viable. - -#### Original Agentforce Accommodation (February 2026) - -We had added full support for Salesforce Agentforce alongside the existing Einstein Bots support, but removed it due to the lack of a reliable integration path. - -#### Key Research Findings - -| Aspect | Einstein Bots | Agentforce | -|--------|--------------|------------| -| **Permission Set** | `sfdc_chatbot_service_permset` (auto-created) | Custom permission set assigned to `EinsteinServiceAgent` user | -| **User Model** | Uses system permission set | Auto-generated user per agent (format: `EinsteinServiceAgent-[AgentId]`) | -| **Fallback Mechanism** | **Confused** topic | **Fallback** topic | -| **AI Architecture** | Rule-based (NLU) | LLM-based (generative) | -| **Prerequisites** | Service Cloud License | Einstein Copilot + Data Cloud | - -**Critical finding:** The same Apex `@InvocableMethod` classes work with both platforms. The integration code is identical—only the permission configuration and topic names differ. - -#### What We Changed - -**1. MS Learn Documentation** (`customer-copilot-salesforce-handoff.md`) - -- Updated title: "Integrate with Salesforce Einstein Bot or Agentforce" -- Added introduction explaining both platforms are supported -- Added MS Learn tabs (`# [Einstein Bots](#tab/einsteinBots)` / `# [Agentforce](#tab/agentforce)`) to: - - Prerequisites (different license requirements) - - Step 5: Grant permissions (different permission set approach) - - Step 6: Set up bot/agent (different setup locations) - - Step 7: Connect to Copilot Studio (Confused vs Fallback topic) - - Step 8: Handle agent handoff (different escalation docs) -- Added new Step 4: Configure Named Credential (was missing from original doc) -- Steps 1-4 remain unchanged (identical for both platforms) - -**2. Deployment Scripts** - -`grant-bot-permissions.sh` / `grant-bot-permissions.ps1`: -- Added `--agentforce` / `-Agentforce` flag -- Added `--agent-name` / `-AgentName` optional filter -- Agentforce mode: - - Creates `CopilotStudio_DirectLine` permission set - - Queries for `EinsteinServiceAgent` users via SOQL - - Auto-assigns permission set if single agent found - - Lists agents if multiple found (requires `--agent-name` filter) - -`deploy.sh` / `deploy.ps1`: -- Added `--agentforce` / `-Agentforce` flag -- Added `--agent-name` / `-AgentName` pass-through -- Shows mode at startup -- Links to correct MS Learn tab in output - -**3. README Updates** - -- Updated intro to mention both platforms -- Added Quick Start examples for both modes -- Updated "What Gets Deployed" to explain both modes -- Renamed "Agentforce Compatibility" to "Agentforce Support" (now fully automated) - -#### Agentforce Agent Creation Order - -**Important:** The Agentforce agent must be created in the Salesforce UI *before* the script can assign permissions to it. The `EinsteinServiceAgent` user is auto-generated when you create an Agentforce agent. - -**Recommended workflow:** -1. Run `deploy.sh --agentforce` to deploy metadata and create permission set -2. Create Agentforce agent in Salesforce Setup → Agents -3. Re-run `grant-bot-permissions.sh --agentforce` to assign permission set to agent user - -**Alternative:** Manually assign the `CopilotStudio_DirectLine` permission set to your agent's user in Setup → Users. - -#### SOQL Queries for Agentforce - -```sql --- Find EinsteinServiceAgent users -SELECT Id, Username FROM User WHERE Username LIKE 'EinsteinServiceAgent%' LIMIT 10 - --- Filter by agent name -SELECT Id, Username FROM User WHERE Username LIKE 'EinsteinServiceAgent%' AND Name LIKE '%AgentName%' LIMIT 10 - --- Check permission set assignment -SELECT Id FROM PermissionSetAssignment WHERE AssigneeId = 'userId' AND PermissionSetId = 'permsetId' -``` - -#### API Limitation: SetupEntityType - -The `SetupEntityType` field on `SetupEntityAccess` is read-only via the API. When creating access records, omit this field - Salesforce auto-determines the type from `SetupEntityId`. - -#### MS Learn Tab Syntax - -```markdown -# [Einstein Bots](#tab/einsteinBots) - -Content for Einstein Bots... - -# [Agentforce](#tab/agentforce) - -Content for Agentforce... - ---- -``` - -Tab IDs are used in URLs: `?tabs=einsteinBots` or `?tabs=agentforce` - ---- - -## File Structure - -``` -Salesforce/ -├── ApexClasses/ -│ ├── DL_GetConversation.cls # Starts DirectLine conversation -│ ├── DL_PostActivity.cls # Sends user message to Copilot Studio -│ └── DL_GetActivity.cls # Retrieves bot response (with polling) -├── Metadata/ -│ ├── classes/ # Apex class metadata XML -│ ├── remoteSiteSettings/ # DirectLine remote site -│ ├── externalCredentials/ # Directline External Credential -│ └── namedCredentials/ # Directline Named Credential -├── scripts/ -│ ├── deploy.sh # Main deployment (macOS/Linux) -│ ├── deploy.ps1 # Main deployment (Windows) -│ ├── grant-bot-permissions.sh # Permission grant (macOS/Linux) -│ └── grant-bot-permissions.ps1 # Permission grant (Windows) -├── README.md -└── CLAUDE.md # This file -``` - ---- - -## Usage Examples - -```bash -# Deploy everything (Einstein Bots) -./scripts/deploy.sh - -# Just grant permissions (skip deployment) -./scripts/grant-bot-permissions.sh -``` - ---- - -## Links - -- [MS Learn Documentation](https://learn.microsoft.com/en-us/microsoft-copilot-studio/customer-copilot-salesforce-handoff) -- [Einstein Bots Tab](https://learn.microsoft.com/en-us/microsoft-copilot-studio/customer-copilot-salesforce-handoff?tabs=einsteinBots) -- [Agentforce Tab](https://learn.microsoft.com/en-us/microsoft-copilot-studio/customer-copilot-salesforce-handoff?tabs=agentforce) -- [Salesforce Einstein Bots Docs](https://help.salesforce.com/s/articleView?language=en_US&id=sf.bots_service_enhanced.htm&type=5) -- [Salesforce Agentforce Docs](https://help.salesforce.com/s/articleView?id=sf.copilot_intro.htm&type=5) diff --git a/contact-center/servicenow/ServiceNow/ScriptIncludes/CustomDirectLineInboundTransformer.js b/contact-center/servicenow/ScriptIncludes/CustomDirectLineInboundTransformer.js similarity index 100% rename from contact-center/servicenow/ServiceNow/ScriptIncludes/CustomDirectLineInboundTransformer.js rename to contact-center/servicenow/ScriptIncludes/CustomDirectLineInboundTransformer.js diff --git a/contact-center/servicenow/ServiceNow/README.md b/contact-center/servicenow/ServiceNow/README.md deleted file mode 100644 index ea33f2e6..00000000 --- a/contact-center/servicenow/ServiceNow/README.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: ServiceNow -parent: Contact Center -nav_order: 3 ---- -# Copilot Studio - ServiceNow Integration Samples - -This folder contains sample code for integrating Microsoft Copilot Studio with ServiceNow Virtual Agent, enabling seamless handoff from virtual agent to live agent. - -## Assets Included - -| Asset | Description | File | -|-------|-------------|------| -| **Azure Function** | Relay service that bridges ServiceNow with the Direct Line API | [`DirectLineAzureFunction/relayToDirectLine`](./DirectLineAzureFunction/) | -| **ServiceNow Script Include** | Custom transformer that detects `handoff.initiate` events and triggers agent escalation | [`ScriptIncludes/CustomDirectLineInboundTransformer.js`](./ScriptIncludes/CustomDirectLineInboundTransformer.js) | - -These samples are companion code for the official documentation at: [Microsoft Learn - Copilot Studio with ServiceNow](https://learn.microsoft.com/en-us/microsoft-copilot-studio/customer-copilot-servicenow) diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/ContosoLiveChatApp.csproj b/contact-center/skill-handoff/ContosoLiveChatApp/ContosoLiveChatApp.csproj similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/ContosoLiveChatApp.csproj rename to contact-center/skill-handoff/ContosoLiveChatApp/ContosoLiveChatApp.csproj diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Controllers/ChatController.cs b/contact-center/skill-handoff/ContosoLiveChatApp/Controllers/ChatController.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Controllers/ChatController.cs rename to contact-center/skill-handoff/ContosoLiveChatApp/Controllers/ChatController.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Models/ChatMessage.cs b/contact-center/skill-handoff/ContosoLiveChatApp/Models/ChatMessage.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Models/ChatMessage.cs rename to contact-center/skill-handoff/ContosoLiveChatApp/Models/ChatMessage.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Program.cs b/contact-center/skill-handoff/ContosoLiveChatApp/Program.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Program.cs rename to contact-center/skill-handoff/ContosoLiveChatApp/Program.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/README.md b/contact-center/skill-handoff/ContosoLiveChatApp/README.md similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/README.md rename to contact-center/skill-handoff/ContosoLiveChatApp/README.md diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Services/ChatStorageService.cs b/contact-center/skill-handoff/ContosoLiveChatApp/Services/ChatStorageService.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Services/ChatStorageService.cs rename to contact-center/skill-handoff/ContosoLiveChatApp/Services/ChatStorageService.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Services/WebhookService.cs b/contact-center/skill-handoff/ContosoLiveChatApp/Services/WebhookService.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/Services/WebhookService.cs rename to contact-center/skill-handoff/ContosoLiveChatApp/Services/WebhookService.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/appsettings.json b/contact-center/skill-handoff/ContosoLiveChatApp/appsettings.json similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/appsettings.json rename to contact-center/skill-handoff/ContosoLiveChatApp/appsettings.json diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/wwwroot/index.html b/contact-center/skill-handoff/ContosoLiveChatApp/wwwroot/index.html similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/ContosoLiveChatApp/wwwroot/index.html rename to contact-center/skill-handoff/ContosoLiveChatApp/wwwroot/index.html diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverAgentSample.zip b/contact-center/skill-handoff/HandoverAgentSample.zip similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverAgentSample.zip rename to contact-center/skill-handoff/HandoverAgentSample.zip diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/CopilotStudio/ConversationManager.cs b/contact-center/skill-handoff/HandoverToLiveAgentSample/CopilotStudio/ConversationManager.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/CopilotStudio/ConversationManager.cs rename to contact-center/skill-handoff/HandoverToLiveAgentSample/CopilotStudio/ConversationManager.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/CopilotStudio/CopilotStudioAgent.cs b/contact-center/skill-handoff/HandoverToLiveAgentSample/CopilotStudio/CopilotStudioAgent.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/CopilotStudio/CopilotStudioAgent.cs rename to contact-center/skill-handoff/HandoverToLiveAgentSample/CopilotStudio/CopilotStudioAgent.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/CopilotStudio/MsTeamsProactiveMesssage.cs b/contact-center/skill-handoff/HandoverToLiveAgentSample/CopilotStudio/MsTeamsProactiveMesssage.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/CopilotStudio/MsTeamsProactiveMesssage.cs rename to contact-center/skill-handoff/HandoverToLiveAgentSample/CopilotStudio/MsTeamsProactiveMesssage.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/HandoverToLiveAgentSample.csproj b/contact-center/skill-handoff/HandoverToLiveAgentSample/HandoverToLiveAgentSample.csproj similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/HandoverToLiveAgentSample.csproj rename to contact-center/skill-handoff/HandoverToLiveAgentSample/HandoverToLiveAgentSample.csproj diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/LiveChat/LiveChatService.cs b/contact-center/skill-handoff/HandoverToLiveAgentSample/LiveChat/LiveChatService.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/LiveChat/LiveChatService.cs rename to contact-center/skill-handoff/HandoverToLiveAgentSample/LiveChat/LiveChatService.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/LiveChat/LiveChatWebhookController.cs b/contact-center/skill-handoff/HandoverToLiveAgentSample/LiveChat/LiveChatWebhookController.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/LiveChat/LiveChatWebhookController.cs rename to contact-center/skill-handoff/HandoverToLiveAgentSample/LiveChat/LiveChatWebhookController.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/Program.cs b/contact-center/skill-handoff/HandoverToLiveAgentSample/Program.cs similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/Program.cs rename to contact-center/skill-handoff/HandoverToLiveAgentSample/Program.cs diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/README.md b/contact-center/skill-handoff/HandoverToLiveAgentSample/README.md similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/README.md rename to contact-center/skill-handoff/HandoverToLiveAgentSample/README.md diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/appsettings.json b/contact-center/skill-handoff/HandoverToLiveAgentSample/appsettings.json similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/appsettings.json rename to contact-center/skill-handoff/HandoverToLiveAgentSample/appsettings.json diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/wwwroot/skill-manifest.json b/contact-center/skill-handoff/HandoverToLiveAgentSample/wwwroot/skill-manifest.json similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/HandoverToLiveAgentSample/wwwroot/skill-manifest.json rename to contact-center/skill-handoff/HandoverToLiveAgentSample/wwwroot/skill-manifest.json diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/README.md b/contact-center/skill-handoff/README.md similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/README.md rename to contact-center/skill-handoff/README.md diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/img/app_registration_setup.png b/contact-center/skill-handoff/img/app_registration_setup.png similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/img/app_registration_setup.png rename to contact-center/skill-handoff/img/app_registration_setup.png diff --git a/contact-center/servicenow/HandoverToLiveAgentUsingSkill/img/solution_import.png b/contact-center/skill-handoff/img/solution_import.png similarity index 100% rename from contact-center/servicenow/HandoverToLiveAgentUsingSkill/img/solution_import.png rename to contact-center/skill-handoff/img/solution_import.png diff --git a/ui/embed/sharepoint-customizer/.eslintrc.js b/ui/embed/sharepoint-customizer/.eslintrc.js index f6e5edb0..562b8f68 100644 --- a/ui/embed/sharepoint-customizer/.eslintrc.js +++ b/ui/embed/sharepoint-customizer/.eslintrc.js @@ -12,48 +12,14 @@ module.exports = { 'sourceType': 'module' }, rules: { - '@typescript-eslint/no-empty-function': 0, // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin - '@rushstack/no-new-null': 0, + '@rushstack/no-new-null': 1, // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin '@rushstack/hoist-jest-mock': 1, // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security '@rushstack/security/no-unsafe-regexp': 1, // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json '@typescript-eslint/adjacent-overload-signatures': 1, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - // - // CONFIGURATION: By default, these are banned: String, Boolean, Number, Object, Symbol - '@typescript-eslint/ban-types': [ - 1, - { - 'extendDefaults': false, - 'types': { - 'String': { - 'message': 'Use \'string\' instead', - 'fixWith': 'string' - }, - 'Boolean': { - 'message': 'Use \'boolean\' instead', - 'fixWith': 'boolean' - }, - 'Number': { - 'message': 'Use \'number\' instead', - 'fixWith': 'number' - }, - 'Object': { - 'message': 'Use \'object\' instead, or else define a proper TypeScript type:' - }, - 'Symbol': { - 'message': 'Use \'symbol\' instead', - 'fixWith': 'symbol' - }, - 'Function': { - 'message': 'The \'Function\' type accepts any function-like value.\nIt provides no type safety when calling the function, which can be a common source of bugs.\nIt also accepts things like class declarations, which will throw at runtime as they will not be called with \'new\'.\nIf you are expecting the function to accept certain arguments, you should explicitly define the function shape.' - } - } - } - ], // RATIONALE: Code is more readable when the type of every variable is immediately obvious. // Even if the compiler may be able to infer a type, this inference will be unavailable // to a person who is reviewing a GitHub diff. This rule makes writing code harder, @@ -61,7 +27,7 @@ module.exports = { // // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json '@typescript-eslint/explicit-function-return-type': [ - 0, + 1, { 'allowExpressions': true, 'allowTypedFunctionExpressions': true, @@ -80,7 +46,7 @@ module.exports = { // This rule should be suppressed only in very special cases such as JSON.stringify() // where the type really can be anything. Even if the type is flexible, another type // may be more appropriate such as "unknown", "{}", or "Record". - '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-explicit-any': 1, // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() // handler. Thus wherever a Promise arises, the code must either append a catch handler, // or else return the object to a caller (who assumes this responsibility). Unterminated @@ -324,7 +290,7 @@ module.exports = { // ==================================================================== '@microsoft/spfx/import-requires-chunk-name': 1, '@microsoft/spfx/no-require-ensure': 2, - '@microsoft/spfx/pair-react-dom-render-unmount': 0 + '@microsoft/spfx/pair-react-dom-render-unmount': 1 } }, { diff --git a/ui/embed/sharepoint-customizer/.vscode/settings.json b/ui/embed/sharepoint-customizer/.vscode/settings.json index a31a2c33..16141662 100644 --- a/ui/embed/sharepoint-customizer/.vscode/settings.json +++ b/ui/embed/sharepoint-customizer/.vscode/settings.json @@ -6,6 +6,7 @@ "**/.DS_Store": true, "**/bower_components": true, "**/coverage": true, + "**/jest-output": true, "**/lib-amd": true, "src/**/*.scss.ts": true }, diff --git a/ui/embed/sharepoint-customizer/.yo-rc.json b/ui/embed/sharepoint-customizer/.yo-rc.json index ffa4db88..571035ec 100644 --- a/ui/embed/sharepoint-customizer/.yo-rc.json +++ b/ui/embed/sharepoint-customizer/.yo-rc.json @@ -2,18 +2,15 @@ "@microsoft/generator-sharepoint": { "plusBeta": false, "isCreatingSolution": true, - "nodeVersion": "16.20.2", - "sdksVersions": { - "@microsoft/microsoft-graph-client": "3.0.2", - "@microsoft/teams-js": "2.12.0" - }, - "version": "1.18.0", - "libraryName": "pva-extension-sso", - "libraryId": "14634225-91e5-41a4-b9cc-161ccb3400b4", + "nodeVersion": "22.17.0", + "sdksVersions": {}, + "version": "1.21.1", + "libraryName": "sidebar-agent", + "libraryId": "54cbba6a-2459-43a9-80a4-bc0675a81ac0", "environment": "spo", "packageManager": "npm", - "solutionName": "pva-extension-sso", - "solutionShortDescription": "pva-extension-sso description", + "solutionName": "sidebar-agent", + "solutionShortDescription": "sidebar-agent description", "skipFeatureDeployment": true, "isDomainIsolated": false, "componentType": "extension", diff --git a/ui/embed/sharepoint-customizer/Configure-McsForSite.ps1 b/ui/embed/sharepoint-customizer/Configure-McsForSite.ps1 deleted file mode 100644 index 2f3d1eb1..00000000 --- a/ui/embed/sharepoint-customizer/Configure-McsForSite.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -param ( - [Parameter(Mandatory=$true)] - [string]$siteUrl, - - [Parameter(Mandatory=$true)] - [string]$botUrl, - - [Parameter(Mandatory=$true)] - [string]$botName, - - [Parameter(Mandatory=$true)] - [string]$customScope, - - [Parameter(Mandatory=$true)] - [string]$clientId, - - [Parameter(Mandatory=$true)] - [string]$authority, - - [Parameter(Mandatory=$true)] - [string]$buttonLabel, - - [Parameter(Mandatory=$true)] - [switch]$greet -) - -Connect-PnPOnline -Url $siteUrl -Interactive -$action = (Get-PnPCustomAction | Where-Object { $_.Title -eq "PvaSso" })[0] - -$action.ClientSideComponentProperties = @{ - "botURL" = $botUrl - "customScope" = $customScope - "clientID" = $clientId - "authority" = $authority - "greet" = $greet.isPresent - "buttonLabel" = $buttonLabel - "botName" = $botName -} | ConvertTo-Json -Compress - -$action.Update() -Invoke-PnPQuery \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/README.md b/ui/embed/sharepoint-customizer/README.md index f631688d..ef3c2346 100644 --- a/ui/embed/sharepoint-customizer/README.md +++ b/ui/embed/sharepoint-customizer/README.md @@ -1,39 +1,253 @@ --- -title: SharePoint Customizer +title: SharePoint Sidepanel parent: Embed grand_parent: UI nav_order: 6 --- -# SharePoint SSO Component +# Copilot Studio Agent Side Panel for SharePoint +![Copilot Studio Agent in SharePoint](./images/agent%20sidepanel.png) -This code sample demonstrates how to create a SharePoint SPFx component which is a wrapper for a copilot, created with Microsoft Copilot Studio. The SPFx component included in the sample supports SSO, providing seamless authentication for users interacting with the copilot. +## Summary + +This sample demonstrates how to integrate a Microsoft Copilot Studio agent into SharePoint using a SharePoint Framework (SPFx) Application Customizer. It leverages the ReactWebChat component with the [Fluent UI theme pack](https://github.com/microsoft/BotFramework-WebChat#experimental-fluent-ui-theme-pack) and the [Microsoft 365 Agents SDK for NodeJS/TypeScript](https://github.com/microsoft/Agents-for-js) to establish a secure connection with a Copilot Studio agent configured with ['Authenticate with Microsoft'](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft). + +The key advantage of using the Microsoft 365 Agents SDK is its ability to connect to agents that support Microsoft authentication, which enables [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) capabilities. {: .important } -> ⚠️ **This sample has been superseded.** -> Please use the updated sample here: [SharePoint SSO App Customizer](./SharePointSSOAppCustomizer/). -> -> The new sample: -> - Uses a **modern UI** -> - Leverages the **M365 Agents SDK**, which supports **tenant Graph grounding** -> - Requires **less complex setup** +> ⚠️ **This is an open-source reference sample only.** +> For production deployments, we strongly recommend using the [built-in SharePoint channel in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-sharepoint#configure-the-sharepoint-channel). + +## Features of this sample + +- **Single Sign-On (SSO)**: Leverages the current user's SharePoint authentication for seamless agent access +- **Sliding Panel UI**: Modern, responsive chat interface that slides in from the right side of the page +- **Customizable Appearance**: Configure header color and agent title to match your branding +- **New Conversation Support**: Users can start fresh conversations without leaving the page +- **Fluent UI Styling**: Uses the Fluent UI theme pack for a native Microsoft 365 look and feel +- **Tenant Graph Grounding**: Enables the agent to access and search Microsoft 365 content through [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) + +## Prerequisites + +Before you begin, ensure you have the following: + +1. **Published Copilot Studio Agent** + - Agent must be configured with ['Authenticate with Microsoft'](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft) authentication + - Agent should be published and accessible + - Consider enabling [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) for access to Microsoft 365 content + +2. **Azure Portal Access** + - Required for creating and configuring app registrations + - Permissions to create new app registrations in your Azure AD tenant + +3. **SharePoint Administrator Access** + - Required to deploy the SPFx extension to the app catalog + +4. **Development Environment** + - Node.js v22 (as per SPFx 1.21.1 requirements) + - npm or yarn package manager + - Git for cloning the repository + +## Technical Architecture + +This solution uses: +- **[Microsoft 365 Agents SDK for NodeJS/TypeScript](https://github.com/microsoft/Agents-for-js)**: Provides secure connection and authentication handling for Copilot Studio agents +- **[BotFramework-WebChat with Fluent UI Theme](https://github.com/microsoft/BotFramework-WebChat#experimental-fluent-ui-theme-pack)**: Renders the chat interface with Microsoft 365 styling +- **SharePoint Framework**: Enables deployment as a site-wide extension +- **[Tenant Graph Grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding)**: Allows the agent to search and access Microsoft 365 content + +## Setup Instructions + +### Step 1: Configure App Registration + +This step requires permissions to create application identities in your Azure tenant. For this sample, create a Native Client Application Identity (no secrets required): + +1. **Open Azure Portal** + - Navigate to [portal.azure.com](https://portal.azure.com) + - Go to **Azure Active Directory** (Entra ID) + +2. **Create a new App Registration** + - Click **App registrations** → **New registration** + - Provide a **Name** (e.g., "SharePoint Copilot Studio Agent") + - Choose **"Accounts in this organization directory only"** + - Under **Redirect URI**: + - Select **"Single-page application"** from the platform dropdown + - Enter your first SharePoint site URL without the trailing slash (e.g., `https://contoso.sharepoint.com/sites/mysite`) + - Click **Register** + +3. **Configure Authentication - Add SharePoint URLs** + - Go to **Authentication** in the Manage section + - Under **Single-page application**, add redirect URIs: + - `https://localhost:4321` (only if you plan to test locally - this is the default SPFx port) + - For each SharePoint site where you'll deploy the extension, add: + - The site URL **without** trailing slash (e.g., `https://contoso.sharepoint.com/sites/mysite`) + - Under **Implicit grant and hybrid flows**, ensure both checkboxes are **unchecked** (SPAs use PKCE flow) + - Click **Save** + + **Example for multiple sites:** + ``` + https://localhost:4321 (for local testing - default SPFx port) + https://contoso.sharepoint.com/sites/hr + https://contoso.sharepoint.com/sites/finance + https://contoso.sharepoint.com/sites/it + ``` + +4. **Configure API Permissions** + - In your new application, go to **API Permissions** in the Manage section + - Click **Add Permission** + - In the side panel, click the **APIs my organization uses** tab + - Search for **"Power Platform API"** or use the GUID `8578e004-a5c6-46e7-913e-12f58912df43` + - Under **Delegated permissions**, expand **CopilotStudio** and check: + - `CopilotStudio.Copilots.Invoke` + - Click **Add Permissions** + - (Optional) Click **Grant admin consent** for your organization + +5. **Note Required Values** + - On the **Overview** page, copy and save: + - **Application (client) ID** (e.g., `12345678-1234-1234-1234-123456789012`) + - **Directory (tenant) ID** (e.g., `87654321-4321-4321-4321-210987654321`) + +{: .note } +> If you don't see "Power Platform API" in the list, you need to add it to your tenant first. See [Power Platform API Authentication](https://learn.microsoft.com/en-us/power-platform/admin/programmability-authentication) and follow Step 2 to add the API. + +### Step 2: Get Copilot Studio Configuration + +1. Open your Copilot Studio environment +2. Navigate to your published agent +3. Ensure the agent is configured with ['Authenticate with Microsoft'](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft) +4. Optionally enable [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) for Microsoft 365 content access +5. Collect the required configuration values using one of these methods: + + **Option A: Get Direct Connect URL (Recommended)** + - Go to **Channels** → **Web app** + - Under **Microsoft 365 Agents SDK**, copy the **Connection string** + - This will look like: `https://xxxxxxx.07.environment.api.powerplatform.com/...` + + **Option B: Get Environment ID and Agent Identifier** + - Go to **Settings** → **Advanced** → **Metadata** + - Copy the following values: + - **Environment ID** + - **Schema name** (this is your Agent Identifier, e.g., `cr770_myAgent`) + +### Step 3: Clone the Repository + +```bash +git clone https://github.com/microsoft/CopilotStudioSamples.git +cd CopilotStudioSamples/ui/embed/sharepoint-customizer +``` + +### Step 4: Configure the Extension + +1. Open `sharepoint/assets/elements.xml` in your editor +2. Update the `ClientSideComponentProperties` with your values: + +```xml +ClientSideComponentProperties="{ + "appClientId": "YOUR_APP_CLIENT_ID", + "tenantId": "YOUR_TENANT_ID", + "environmentId": "YOUR_ENVIRONMENT_ID", + "agentIdentifier": "YOUR_AGENT_IDENTIFIER", + "directConnectUrl": "YOUR_DIRECT_CONNECT_URL", + "showTyping": true, + "headerBackgroundColor": "white", + "agentTitle": "Copilot Studio Agent" +}" +``` + +**Configuration Options:** + +| Property | Required | Description | Default | +|----------|----------|-------------|---------| +| `appClientId` | Yes | Your Azure AD app registration client ID | - | +| `tenantId` | Yes | Your Azure AD tenant ID | - | +| `environmentId` | Conditional | Copilot Studio environment ID (required if not using directConnectUrl) | - | +| `agentIdentifier` | Conditional | Your agent's identifier (required if not using directConnectUrl) | - | +| `directConnectUrl` | Conditional | Direct connection URL (use this OR environmentId + agentIdentifier) | - | +| `showTyping` | No | Show typing indicators | `true` | +| `headerBackgroundColor` | No | Header bar color (accepts any CSS color value) | `white` | +| `agentTitle` | No | Display title for the agent | `"Copilot Studio Agent"` | + +{: .note } +> You must provide either `directConnectUrl` OR both `environmentId` and `agentIdentifier`. + +### Step 5: Test Locally (Optional) + +Before building and deploying, you can test the extension locally: + +```bash +# Install dependencies +npm install + +# Check if gulp is installed, if not, install it +which gulp || npm install -g gulp-cli +# On Windows, use: where gulp || npm install -g gulp-cli + +# Serve the solution locally +gulp serve --nobrowser +``` + +After running `gulp serve`, navigate to the following URL (replace the values with your configuration): + +{% raw %} +``` +https://YOUR-TENANT.sharepoint.com/sites/YOUR-SITE/SitePages/Home.aspx?debugManifestsFile=https://localhost:4321/temp/build/manifests.js&loadSPFX=true&customActions={%224c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2%22:{%22location%22:%22ClientSideExtension.ApplicationCustomizer%22,%22properties%22:{%22appClientId%22:%22YOUR_APP_CLIENT_ID%22,%22tenantId%22:%22YOUR_TENANT_ID%22,%22directConnectUrl%22:%22YOUR_DIRECT_CONNECT_URL%22,%22showTyping%22:true,%22headerBackgroundColor%22:%22white%22,%22agentTitle%22:%22Your%20Agent%20Title%22}}} +``` +{% endraw %} + +- Replace `YOUR-TENANT`, `YOUR-SITE`, `YOUR_APP_CLIENT_ID`, `YOUR_TENANT_ID`, and `YOUR_DIRECT_CONNECT_URL` with your actual values +- Ensure you've added `https://localhost:4321` to your app registration's redirect URIs if testing locally + +### Step 6: Build and Package + +Once you've tested locally (or if you're ready to deploy directly): + +```bash +# Build the solution +gulp build + +# Bundle the solution +gulp bundle --ship + +# Package the solution +gulp package-solution --ship +``` + +This will create a `.sppkg` file in the `sharepoint/solution` folder. + +### Step 7: Deploy to SharePoint + +Upload the `.sppkg` file from `sharepoint/solution` to your SharePoint App Catalog. For detailed instructions on creating an app catalog, deploying apps, and adding them to sites, see [Add custom apps to SharePoint](https://learn.microsoft.com/en-us/sharepoint/use-app-catalog#add-custom-apps). + +## Troubleshooting + +### "Unable to acquire token" error + +If you encounter this error: +1. Open your browser's Developer Tools (F12) +2. Go to the **Network** tab +3. Look for failed authentication requests (usually to `login.microsoftonline.com`) +4. Click on the failed requests to see detailed error messages +5. Follow the authentication URLs that MSAL is attempting - the actual error details are often revealed in the response or redirect parameters -## Getting Started +Common causes: +- Missing or incorrect redirect URIs in your app registration (check for trailing slashes!) +- Incorrect permissions in the app registration (ensure `CopilotStudio.Copilots.Invoke` is granted) +- Incorrect tenant ID or client ID +- MSAL authentication flow being blocked by browser settings -1. Create an app registration in Azure and configure authentication settings for your copilot in Copilot Studio -2. Create an app registration for your SharePoint site -3. Clone this repo and cd into the SharePointSSOComponent folder -4. Install the dependencies and build the component: +### "The redirect URI specified in the request does not match the redirect URIs configured for the application" error - ```shell - npm install - gulp bundle --ship - gulp package-solution --ship - ``` +Check that the current SharePoint URL has been added as an allowed redirect URI for the provided app registration. -5. Upload the component to your tenant app catalog and enable on your site +## Additional Resources -For more detailed instructions, please refer to the [step-by-step setup guide](./SETUP). +- [Built-in SharePoint Channel for Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-sharepoint#configure-the-sharepoint-channel) +- [Authenticate with Microsoft in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft) +- [Tenant Graph Grounding in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) +- [Microsoft 365 Agents SDK for NodeJS/TypeScript](https://github.com/microsoft/Agents-for-js) +- [BotFramework-WebChat with Fluent UI Theme](https://github.com/microsoft/BotFramework-WebChat#experimental-fluent-ui-theme-pack) +- [Copilot Studio Documentation](https://learn.microsoft.com/en-us/microsoft-copilot-studio/) +- [SharePoint Framework Documentation](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/sharepoint-framework-overview) -## The Deployed Component +## License -![Microsoft Copilot Studio SSO](./images/SharePointSSOComponent.png) +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/ui/embed/sharepoint-customizer/SETUP.md b/ui/embed/sharepoint-customizer/SETUP.md deleted file mode 100644 index 2b3410fd..00000000 --- a/ui/embed/sharepoint-customizer/SETUP.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -nav_exclude: true -search_exclude: false ---- -# Deploy a Microsoft Copilot Studio copilot as a SharePoint component with single sign-on (SSO) enabled. - -To follow through the end-to-end setup process, you would need to: - -1. Configure Microsoft Entra ID authentication for your copilot. -2. Register your SharePoint site as a canvas app – an application that will host your copilot and handle the single sign-on flow. -3. Build the SharePoint component and configure its properties based on values from step (2). -4. Upload the component to SharePoint and add the component to your site. - -## Step 1 - Configure Microsoft Entra ID authentication for your copilot - -This step can be completed mostly by following the instructions here: [Configure user authentication with Microsoft Entra ID](https://learn.microsoft.com/en-us/power-virtual-agents/configuration-authentication-azure-ad), with some added configuration which is specified below. - -1. **Optional – add scopes for SharePoint and OneDrive**. For your copilot to use the Generative Answers capability over a SharePoint or OneDrive data source, you would need to configure additional scopes for the API permissions assigned to your app. Please refer to [Generative answers with Search and summarize: Authentication](https://learn.microsoft.com/en-us/power-virtual-agents/nlu-boost-node#authentication). - - -

- API Permissions -
- API Permissions of the copilot app registration -

- - -2. **Mandatory – populate the token exchange URL in the copilot’s authentication settings.** Your copilot will send this URL to any custom application hosting it, instructing the custom application it should sign users in by acquiring a token matching this custom scope. The value for “token exchange URL” is the full URI for the custom scope you have added when configuring a custom API. - -

- Custom Scope -
- The custom scope for the copilot app registration -

-
-

- Authentication Settings -
- Authentication configuration of the copilot, including token exchange URL -

- - -Once all the steps under [Configure user authentication with Microsoft Entra ID](https://learn.microsoft.com/en-us/power-virtual-agents/configuration-authentication-azure-ad) have been completed and the optional additional scopes have been specified, you should be able to use Generative Answers over a SharePoint or OneDrive data source from the Microsoft Copilot Studio authoring experience. Please refer to [Use content on SharePoint or OneDrive for Business for generative answers](https://learn.microsoft.com/en-us/power-virtual-agents/nlu-generative-answers-sharepoint-onedrive) for instructions on add a SharePoint or OneDrive data source for your Copilot Generative Answers node. - -Before moving to Step 2, make sure the Copilot Studio authoring canvas can successfully sign you in. If "Require users to sign in" is selected in the authentication settings, the canvas will try to sign in you in as soon as the conversation starts. Otherwise, the-sign in topic will have to be triggered by a specific event in the conversation. In case Generative Answers is configured over SharePoint or OneDrive, please make sure your copilot responds to questions as expected. - -**Important:** For now, the copilot canvas will use a validation code to sign you in, but once the setup is complete, users will be signed-in seamlessly. - -## Step 2 - Register your SharePoint site as a custom canvas - -A custom canvas is a custom application that hosts your copilot. In our case, it is also the application that will be responsible for a seamless sign-in experience. - -In order to configure your SharePoint site as a canvas application with single sign-on enabled, follow the steps specified in [Configure single sign-on with Microsoft Entra ID](https://learn.microsoft.com/en-us/power-virtual-agents/configure-sso?tabs=webApp#create-app-registrations-for-your-custom-website). - -When configuring the canvas app registration, pay attention to the following details: - -1. When adding a platform to the canvas app registration, select “Single-page application” and not “Web”. Web redirect URIs only support the implicit grant flow for authentication, which is considered less secure and cannot be used with MSAL.js 2.x, which is the authentication library included in the code sample provided here. For a discussion about the differences between Web and SPA redirects, please refer to: [https://github.com/MicrosoftDocs/azure-docs/issues/70484#issuecomment-791077654](https://github.com/MicrosoftDocs/azure-docs/issues/70484#issuecomment-791077654) - -2. The redirect URI should be the same as the URL for your SharePoint site that will host the copilot. For example, if you plan to deploy the copilot on , set this as your redirect URI. - - **Important:** Users can reach your SharePoint site via addresses that include trailing slashes. Since redirect URIs are sensitive to this variation, consider creating two redirect URIs representing the same site, with and without a trailing slash (for example: and ) - -3. The canvas app registration will need permissions for the custom API that was configured in *Step 1*. To add this permission, select an API from “APIs my organization uses” and search for the name you have given your copilot app registration in *Step 1*. For example, if your copilot app registration is called “SharePoint Bot Authentication” search for that name in the list of APIs, and select your custom scope (a name for your custom scope has been selected while configuring a custom API for your copilot app registration) - -

- APIs my organization uses -
- The API can be found under “APIs my organization uses” -

-
-

- Permissions for the custom scope -
- Selecting the scope for the API -

- -4. After registering your canvas app, you will not have to use the code sample the page refers to. The code sample provided is a standalone web page implementing SSO for Microsoft Copilot Studio which can be used for testing purposes, but it is not a SharePoint component. - - However, you will need to document the Application (client) ID for the SharePoint component configuration in the next step. - -

- Document the Client ID -
- The Application (client) ID -

- - - -## Step 3 - Download and configure the SharePoint SPFx component - -At this point you have a choice whether to configure and build the component yourself, or use the pre-built package that is included with this sample. Since this is only a reference sample, we encourage you to build the component yourself, but if you choose to deploy the pre-built package, skip ahead to **step 4** - -1. Make sure your development environment includes the following tools and libraries: - 1. VS Code (or a similar code editor) - 2. A version of Node.JS which is [supported by the SPFx framework](https://learn.microsoft.com/en-us/sharepoint/dev/spfx/compatibility#spfx-development-environment-compatibility) (for this sample, use either v16 or v18) - 3. A [Git](https://git-scm.com/downloads) client for your OS -2. If the prerequisites above are satisfied, clone [CopilotStudioSamples (github.com)](https://github.com/microsoft/CopilotStudioSamples) into a local folder. - - In this repo, you will find the SharePointSSOComponent project, which is a code sample for a SharePoint SPFx component (an Application Customizer), which renders a copilot at the bottom of all pages on a specific site. This SPFx component uses the MSAL library to perform a silent login and shares the user’s token with Microsoft Copilot Studio, providing a seamless single sign-on experience. -3. Using Visual Studio Code, open the local folder to which you have cloned the repository. The folder structure should look like below: - -

- The project folder structure -
- The Project Folder Structure -

- - -1. Locate elements.xml under SharePointSSOComponent/sharepoint/assets, and update the values in the file, using one of the two following options: - - *Option 1*: run the following python script and provide values based values from Steps 1 & 2 - - ```python - python .\populate_elements_xml.py - ``` - - *Option 2*: manually replace placeholders in elements.xml with actual values. ClientSideComponentProperties accepts an escaped JSON string. - - ```xml - ClientSideComponentProperties="{"botURL":"YOUR_BOT_URL","customScope":"YOUR_CUSTOM_SCOPE","clientID":"YOU_CLIENT_ID","authority":"YOUR_AAD_LOGIN_URL","greet":TRUE,"buttonLabel":"CHAT_BUTTON_LABEL","botName":"BOT_NAME"}" - ``` - - - *Option 3*: leave elements.xml without changing any details, build and deploy the component on a site, and later configure the component by running [Configure-MCSForSite.ps1](./Configure-MCSForSite.ps1) (see instructions on how to run this script in step 4) - - - ### Property details - - |Property Name|Explanation|Mandatory?| - | :- | :- | :- | - |botURL|The token endpoint for MCS. This can be found in the MCS designer, under Settings -> Channels -> Mobile App|Yes| - |customScope|

The scope defined for the custom API in the copilot app registration (Step 1). For example:

api://35337616-eee1-4049-9d37-a78b24c3bef2/SPO.Read

|Yes| - |clientID|The Application ID from the Canvas app registration configured in step 2|Yes| - |authority|

The login URL for your tenant. For example:
https://login.microsoftonline.com/mytenant.onmicrosoft.com|Yes| - |greet|Should the copilot greet users at the beginning of the conversation|No| - |buttonLabel|The label for the button opening the chat dialog|No| - |botName|The title for the copilot dialog|No| - -
- -5. after populating properties in elements.xml, or if you left elements.xml untouched and plan to run Configure-McsForSite.ps1 after building and deploying the component, open a new terminal in VS Code and navigate to the solution folder (the SharePointSSOComponent folder). Run the following commands: - - ```shell - npm install - gulp bundle --ship - gulp package-solution --ship - ``` - - if gulp is not available, install it by running: - - ```shell - npm install gulp-cli --global - ``` - -6. The gulp package-solution command should create a packaged solution (.sppkg) in the sharepoint/solution folder - -## Step 4 – Upload the component to SharePoint - -1. Whether you have built the component yourself, or opted to use the pre-built package, you should see a file called **pva-extension-sso.sppkg** under [sharepoint/solution](./sharepoint/solution/). Follow the instructions in [Manage apps using the Apps site - SharePoint - SharePoint in Microsoft 365 | Microsoft Learn](https://learn.microsoft.com/en-us/sharepoint/use-app-catalog#add-custom-apps) to upload the sppkg file using your SharePoint admin center. After uploading the sppkg file, choose **Enable App** and not **Enable this app and add it to all sites**. - - Once the app has been successfully uploaded and enabled, it will be visible under “Apps for SharePoint” - -2. Add the app to a site where your copilot should be available for users. This should be the same site as the one you provided for “Redirect URI” in step 2. - - To add an app to your site, follow the instructions in: [Add an app to a site - Microsoft Support](https://support.microsoft.com/en-us/office/add-an-app-to-a-site-ef9c0dbd-7fe1-4715-a1b0-fe3bc81317cb?ui=en-us&rs=en-us&ad=us). - -3. If you left elements.xml untouched, or if you are uploading the pre-built package, or even in case you would like to override the values configured in elements.xml for the site on which the component has been deployed, you can now run [Configure-McsForSite.ps1](./Configure-McsForSite.ps1): - -```PowerShell -.\Configure-McsForSite.ps1 -siteUrl "" -botUrl "" -botName "" -greet $True -customScope "" -clientId "" -authority "" -buttonLabel "" -``` - -4. After adding the app (and running Configure-MCSForSite.ps1 in case elements.xml has been left untouched), a button will be appear at the bottom of all the pages under the target site. Clicking on the button will open a dialog with a chat canvas for your copilot. Based on the logic of your copilot, users will be signed in automatically at the beginning of the conversation, or when a specific event occurs. - -

- Copilot Component -
- The Copilot Component Dialog -

- - diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.eslintrc.js b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.eslintrc.js deleted file mode 100644 index 562b8f68..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.eslintrc.js +++ /dev/null @@ -1,319 +0,0 @@ -require('@rushstack/eslint-config/patch/modern-module-resolution'); -module.exports = { - extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], - parserOptions: { tsconfigRootDir: __dirname }, - overrides: [ - { - files: ['*.ts', '*.tsx'], - parser: '@typescript-eslint/parser', - 'parserOptions': { - 'project': './tsconfig.json', - 'ecmaVersion': 2018, - 'sourceType': 'module' - }, - rules: { - // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin - '@rushstack/no-new-null': 1, - // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin - '@rushstack/hoist-jest-mock': 1, - // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security - '@rushstack/security/no-unsafe-regexp': 1, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/adjacent-overload-signatures': 1, - // RATIONALE: Code is more readable when the type of every variable is immediately obvious. - // Even if the compiler may be able to infer a type, this inference will be unavailable - // to a person who is reviewing a GitHub diff. This rule makes writing code harder, - // but writing code is a much less important activity than reading it. - // - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/explicit-function-return-type': [ - 1, - { - 'allowExpressions': true, - 'allowTypedFunctionExpressions': true, - 'allowHigherOrderFunctions': false - } - ], - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. - // Set to 1 (warning) or 2 (error) to enable. - '@typescript-eslint/explicit-member-accessibility': 0, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/no-array-constructor': 1, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - // - // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. - // This rule should be suppressed only in very special cases such as JSON.stringify() - // where the type really can be anything. Even if the type is flexible, another type - // may be more appropriate such as "unknown", "{}", or "Record". - '@typescript-eslint/no-explicit-any': 1, - // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() - // handler. Thus wherever a Promise arises, the code must either append a catch handler, - // or else return the object to a caller (who assumes this responsibility). Unterminated - // promise chains are a serious issue. Besides causing errors to be silently ignored, - // they can also cause a NodeJS process to terminate unexpectedly. - '@typescript-eslint/no-floating-promises': 2, - // RATIONALE: Catches a common coding mistake. - '@typescript-eslint/no-for-in-array': 2, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/no-misused-new': 2, - // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks - // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler - // optimizations. If you are declaring loose functions/variables, it's better to make them - // static members of a class, since classes support property getters and their private - // members are accessible by unit tests. Also, the exercise of choosing a meaningful - // class name tends to produce more discoverable APIs: for example, search+replacing - // the function "reverse()" is likely to return many false matches, whereas if we always - // write "Text.reverse()" is more unique. For large scale organization, it's recommended - // to decompose your code into separate NPM packages, which ensures that component - // dependencies are tracked more conscientiously. - // - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/no-namespace': [ - 1, - { - 'allowDeclarations': false, - 'allowDefinitionFiles': false - } - ], - // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" - // that avoids the effort of declaring "title" as a field. This TypeScript feature makes - // code easier to write, but arguably sacrifices readability: In the notes for - // "@typescript-eslint/member-ordering" we pointed out that fields are central to - // a class's design, so we wouldn't want to bury them in a constructor signature - // just to save some typing. - // - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - // Set to 1 (warning) or 2 (error) to enable the rule - '@typescript-eslint/parameter-properties': 0, - // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code - // may impact performance. - // - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/no-unused-vars': [ - 1, - { - 'vars': 'all', - // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, - // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures - // that are overriding a base class method or implementing an interface. - 'args': 'none' - } - ], - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/no-use-before-define': [ - 2, - { - 'functions': false, - 'classes': true, - 'variables': true, - 'enums': true, - 'typedefs': true - } - ], - // Disallows require statements except in import statements. - // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. - '@typescript-eslint/no-var-requires': 'error', - // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. - // - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - '@typescript-eslint/prefer-namespace-keyword': 1, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - // Rationale to disable: it's up to developer to decide if he wants to add type annotations - // Set to 1 (warning) or 2 (error) to enable the rule - '@typescript-eslint/no-inferrable-types': 0, - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios - '@typescript-eslint/no-empty-interface': 0, - // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. - 'accessor-pairs': 1, - // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. - 'dot-notation': [ - 1, - { - 'allowPattern': '^_' - } - ], - // RATIONALE: Catches code that is likely to be incorrect - 'eqeqeq': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'for-direction': 1, - // RATIONALE: Catches a common coding mistake. - 'guard-for-in': 2, - // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time - // to split up your code. - 'max-lines': ['warn', { max: 2000 }], - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-async-promise-executor': 2, - // RATIONALE: Deprecated language feature. - 'no-caller': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-compare-neg-zero': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-cond-assign': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-constant-condition': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-control-regex': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-debugger': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-delete-var': 2, - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-duplicate-case': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-empty': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-empty-character-class': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-empty-pattern': 1, - // RATIONALE: Eval is a security concern and a performance concern. - 'no-eval': 1, - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-ex-assign': 2, - // RATIONALE: System types are global and should not be tampered with in a scalable code base. - // If two different libraries (or two versions of the same library) both try to modify - // a type, only one of them can win. Polyfills are acceptable because they implement - // a standardized interoperable contract, but polyfills are generally coded in plain - // JavaScript. - 'no-extend-native': 1, - // Disallow unnecessary labels - 'no-extra-label': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-fallthrough': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-func-assign': 1, - // RATIONALE: Catches a common coding mistake. - 'no-implied-eval': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-invalid-regexp': 2, - // RATIONALE: Catches a common coding mistake. - 'no-label-var': 2, - // RATIONALE: Eliminates redundant code. - 'no-lone-blocks': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-misleading-character-class': 2, - // RATIONALE: Catches a common coding mistake. - 'no-multi-str': 2, - // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to - // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", - // or else implies that the constructor is doing nontrivial computations, which is often - // a poor class design. - 'no-new': 1, - // RATIONALE: Obsolete language feature that is deprecated. - 'no-new-func': 2, - // RATIONALE: Obsolete language feature that is deprecated. - 'no-new-object': 2, - // RATIONALE: Obsolete notation. - 'no-new-wrappers': 1, - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-octal': 2, - // RATIONALE: Catches code that is likely to be incorrect - 'no-octal-escape': 2, - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-regex-spaces': 2, - // RATIONALE: Catches a common coding mistake. - 'no-return-assign': 2, - // RATIONALE: Security risk. - 'no-script-url': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-self-assign': 2, - // RATIONALE: Catches a common coding mistake. - 'no-self-compare': 2, - // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use - // commas to create compound expressions. In general code is more readable if each - // step is split onto a separate line. This also makes it easier to set breakpoints - // in the debugger. - 'no-sequences': 1, - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-shadow-restricted-names': 2, - // RATIONALE: Obsolete language feature that is deprecated. - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-sparse-arrays': 2, - // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, - // such flexibility adds pointless complexity, by requiring every catch block to test - // the type of the object that it receives. Whereas if catch blocks can always assume - // that their object implements the "Error" contract, then the code is simpler, and - // we generally get useful additional information like a call stack. - 'no-throw-literal': 2, - // RATIONALE: Catches a common coding mistake. - 'no-unmodified-loop-condition': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-unsafe-finally': 2, - // RATIONALE: Catches a common coding mistake. - 'no-unused-expressions': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-unused-labels': 1, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-useless-catch': 1, - // RATIONALE: Avoids a potential performance problem. - 'no-useless-concat': 1, - // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. - // Always use "let" or "const" instead. - // - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json - 'no-var': 2, - // RATIONALE: Generally not needed in modern code. - 'no-void': 1, - // RATIONALE: Obsolete language feature that is deprecated. - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'no-with': 2, - // RATIONALE: Makes logic easier to understand, since constants always have a known value - // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js - 'prefer-const': 1, - // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. - 'promise/param-names': 2, - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'require-atomic-updates': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'require-yield': 1, - // "Use strict" is redundant when using the TypeScript compiler. - 'strict': [ - 2, - 'never' - ], - // RATIONALE: Catches code that is likely to be incorrect - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - 'use-isnan': 2, - // STANDARDIZED BY: eslint\conf\eslint-recommended.js - // Set to 1 (warning) or 2 (error) to enable. - // Rationale to disable: !!{} - 'no-extra-boolean-cast': 0, - // ==================================================================== - // @microsoft/eslint-plugin-spfx - // ==================================================================== - '@microsoft/spfx/import-requires-chunk-name': 1, - '@microsoft/spfx/no-require-ensure': 2, - '@microsoft/spfx/pair-react-dom-render-unmount': 1 - } - }, - { - // For unit tests, we can be a little bit less strict. The settings below revise the - // defaults specified in the extended configurations, as well as above. - files: [ - // Test files - '*.test.ts', - '*.test.tsx', - '*.spec.ts', - '*.spec.tsx', - - // Facebook convention - '**/__mocks__/*.ts', - '**/__mocks__/*.tsx', - '**/__tests__/*.ts', - '**/__tests__/*.tsx', - - // Microsoft convention - '**/test/*.ts', - '**/test/*.tsx' - ], - rules: {} - } - ] -}; \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.gitignore b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.gitignore deleted file mode 100644 index 1eac77fa..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Dependency directories -node_modules - -# Build generated files -dist -lib -release -solution/* -!solution/*.sppkg -temp -.heft - -# Coverage directory used by tools like istanbul -coverage - -# OSX -.DS_Store -gulp -# Visual Studio files -.ntvs_analysis.dat -.vs -bin -obj - -# Resx Generated Code -*.resx.ts - -# Styles Generated Code -*.scss.ts diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.npmignore b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.npmignore deleted file mode 100644 index ae0b487c..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.npmignore +++ /dev/null @@ -1,16 +0,0 @@ -!dist -config - -gulpfile.js - -release -src -temp - -tsconfig.json -tslint.json - -*.log - -.yo-rc.json -.vscode diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.vscode/launch.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.vscode/launch.json deleted file mode 100644 index 53ca22cd..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.vscode/launch.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Hosted workbench", - "type": "msedge", - "request": "launch", - "url": "https://{tenantDomain}/_layouts/workbench.aspx", - "webRoot": "${workspaceRoot}", - "sourceMaps": true, - "sourceMapPathOverrides": { - "webpack:///.././src/*": "${webRoot}/src/*", - "webpack:///../../../src/*": "${webRoot}/src/*", - "webpack:///../../../../src/*": "${webRoot}/src/*", - "webpack:///../../../../../src/*": "${webRoot}/src/*" - }, - "runtimeArgs": [ - "--remote-debugging-port=9222", - "-incognito" - ] - } - ] -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.vscode/settings.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.vscode/settings.json deleted file mode 100644 index 16141662..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.vscode/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -// Place your settings in this file to overwrite default and user settings. -{ - // Configure glob patterns for excluding files and folders in the file explorer. - "files.exclude": { - "**/.git": true, - "**/.DS_Store": true, - "**/bower_components": true, - "**/coverage": true, - "**/jest-output": true, - "**/lib-amd": true, - "src/**/*.scss.ts": true - }, - "typescript.tsdk": ".\\node_modules\\typescript\\lib" -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.yo-rc.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.yo-rc.json deleted file mode 100644 index 571035ec..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/.yo-rc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@microsoft/generator-sharepoint": { - "plusBeta": false, - "isCreatingSolution": true, - "nodeVersion": "22.17.0", - "sdksVersions": {}, - "version": "1.21.1", - "libraryName": "sidebar-agent", - "libraryId": "54cbba6a-2459-43a9-80a4-bc0675a81ac0", - "environment": "spo", - "packageManager": "npm", - "solutionName": "sidebar-agent", - "solutionShortDescription": "sidebar-agent description", - "skipFeatureDeployment": true, - "isDomainIsolated": false, - "componentType": "extension", - "extensionType": "ApplicationCustomizer" - } -} diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/README.md b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/README.md deleted file mode 100644 index 996653d5..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/README.md +++ /dev/null @@ -1,253 +0,0 @@ ---- -nav_exclude: true -search_exclude: false ---- -# Copilot Studio Agent Side Panel for SharePoint -![Copilot Studio Agent in SharePoint](./images/agent%20sidepanel.png) - -## Summary - -This sample demonstrates how to integrate a Microsoft Copilot Studio agent into SharePoint using a SharePoint Framework (SPFx) Application Customizer. It leverages the ReactWebChat component with the [Fluent UI theme pack](https://github.com/microsoft/BotFramework-WebChat#experimental-fluent-ui-theme-pack) and the [Microsoft 365 Agents SDK for NodeJS/TypeScript](https://github.com/microsoft/Agents-for-js) to establish a secure connection with a Copilot Studio agent configured with ['Authenticate with Microsoft'](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft). - -The key advantage of using the Microsoft 365 Agents SDK is its ability to connect to agents that support Microsoft authentication, which enables [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) capabilities. - -> **Note**: This sample supersedes the [legacy SharePoint SSO Component sample](../) which uses DirectLine and a modal dialog. - -{: .important } -> ⚠️ **This is an open-source reference sample only.** -> For production deployments, we strongly recommend using the [built-in SharePoint channel in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-sharepoint#configure-the-sharepoint-channel). - -## Features of this sample - -- **Single Sign-On (SSO)**: Leverages the current user's SharePoint authentication for seamless agent access -- **Sliding Panel UI**: Modern, responsive chat interface that slides in from the right side of the page -- **Customizable Appearance**: Configure header color and agent title to match your branding -- **New Conversation Support**: Users can start fresh conversations without leaving the page -- **Fluent UI Styling**: Uses the Fluent UI theme pack for a native Microsoft 365 look and feel -- **Tenant Graph Grounding**: Enables the agent to access and search Microsoft 365 content through [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) - -## Prerequisites - -Before you begin, ensure you have the following: - -1. **Published Copilot Studio Agent** - - Agent must be configured with ['Authenticate with Microsoft'](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft) authentication - - Agent should be published and accessible - - Consider enabling [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) for access to Microsoft 365 content - -2. **Azure Portal Access** - - Required for creating and configuring app registrations - - Permissions to create new app registrations in your Azure AD tenant - -3. **SharePoint Administrator Access** - - Required to deploy the SPFx extension to the app catalog - -4. **Development Environment** - - Node.js v22 (as per SPFx 1.21.1 requirements) - - npm or yarn package manager - - Git for cloning the repository - -## Technical Architecture - -This solution uses: -- **[Microsoft 365 Agents SDK for NodeJS/TypeScript](https://github.com/microsoft/Agents-for-js)**: Provides secure connection and authentication handling for Copilot Studio agents -- **[BotFramework-WebChat with Fluent UI Theme](https://github.com/microsoft/BotFramework-WebChat#experimental-fluent-ui-theme-pack)**: Renders the chat interface with Microsoft 365 styling -- **SharePoint Framework**: Enables deployment as a site-wide extension -- **[Tenant Graph Grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding)**: Allows the agent to search and access Microsoft 365 content - -## Setup Instructions - -### Step 1: Configure App Registration - -This step requires permissions to create application identities in your Azure tenant. For this sample, create a Native Client Application Identity (no secrets required): - -1. **Open Azure Portal** - - Navigate to [portal.azure.com](https://portal.azure.com) - - Go to **Azure Active Directory** (Entra ID) - -2. **Create a new App Registration** - - Click **App registrations** → **New registration** - - Provide a **Name** (e.g., "SharePoint Copilot Studio Agent") - - Choose **"Accounts in this organization directory only"** - - Under **Redirect URI**: - - Select **"Single-page application"** from the platform dropdown - - Enter your first SharePoint site URL without the trailing slash (e.g., `https://contoso.sharepoint.com/sites/mysite`) - - Click **Register** - -3. **Configure Authentication - Add SharePoint URLs** - - Go to **Authentication** in the Manage section - - Under **Single-page application**, add redirect URIs: - - `https://localhost:4321` (only if you plan to test locally - this is the default SPFx port) - - For each SharePoint site where you'll deploy the extension, add: - - The site URL **without** trailing slash (e.g., `https://contoso.sharepoint.com/sites/mysite`) - - Under **Implicit grant and hybrid flows**, ensure both checkboxes are **unchecked** (SPAs use PKCE flow) - - Click **Save** - - **Example for multiple sites:** - ``` - https://localhost:4321 (for local testing - default SPFx port) - https://contoso.sharepoint.com/sites/hr - https://contoso.sharepoint.com/sites/finance - https://contoso.sharepoint.com/sites/it - ``` - -4. **Configure API Permissions** - - In your new application, go to **API Permissions** in the Manage section - - Click **Add Permission** - - In the side panel, click the **APIs my organization uses** tab - - Search for **"Power Platform API"** or use the GUID `8578e004-a5c6-46e7-913e-12f58912df43` - - Under **Delegated permissions**, expand **CopilotStudio** and check: - - `CopilotStudio.Copilots.Invoke` - - Click **Add Permissions** - - (Optional) Click **Grant admin consent** for your organization - -5. **Note Required Values** - - On the **Overview** page, copy and save: - - **Application (client) ID** (e.g., `12345678-1234-1234-1234-123456789012`) - - **Directory (tenant) ID** (e.g., `87654321-4321-4321-4321-210987654321`) - -{: .note } -> If you don't see "Power Platform API" in the list, you need to add it to your tenant first. See [Power Platform API Authentication](https://learn.microsoft.com/en-us/power-platform/admin/programmability-authentication) and follow Step 2 to add the API. - -### Step 2: Get Copilot Studio Configuration - -1. Open your Copilot Studio environment -2. Navigate to your published agent -3. Ensure the agent is configured with ['Authenticate with Microsoft'](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft) -4. Optionally enable [tenant graph grounding](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) for Microsoft 365 content access -5. Collect the required configuration values using one of these methods: - - **Option A: Get Direct Connect URL (Recommended)** - - Go to **Channels** → **Web app** - - Under **Microsoft 365 Agents SDK**, copy the **Connection string** - - This will look like: `https://xxxxxxx.07.environment.api.powerplatform.com/...` - - **Option B: Get Environment ID and Agent Identifier** - - Go to **Settings** → **Advanced** → **Metadata** - - Copy the following values: - - **Environment ID** - - **Schema name** (this is your Agent Identifier, e.g., `cr770_myAgent`) - -### Step 3: Clone the Repository - -```bash -git clone https://github.com/microsoft/CopilotStudioSamples.git -cd CopilotStudioSamples/SSOSamples/SharePointSSOAppCustomizer -``` - -### Step 4: Configure the Extension - -1. Open `sharepoint/assets/elements.xml` in your editor -2. Update the `ClientSideComponentProperties` with your values: - -```xml -ClientSideComponentProperties="{ - "appClientId": "YOUR_APP_CLIENT_ID", - "tenantId": "YOUR_TENANT_ID", - "environmentId": "YOUR_ENVIRONMENT_ID", - "agentIdentifier": "YOUR_AGENT_IDENTIFIER", - "directConnectUrl": "YOUR_DIRECT_CONNECT_URL", - "showTyping": true, - "headerBackgroundColor": "white", - "agentTitle": "Copilot Studio Agent" -}" -``` - -**Configuration Options:** - -| Property | Required | Description | Default | -|----------|----------|-------------|---------| -| `appClientId` | Yes | Your Azure AD app registration client ID | - | -| `tenantId` | Yes | Your Azure AD tenant ID | - | -| `environmentId` | Conditional | Copilot Studio environment ID (required if not using directConnectUrl) | - | -| `agentIdentifier` | Conditional | Your agent's identifier (required if not using directConnectUrl) | - | -| `directConnectUrl` | Conditional | Direct connection URL (use this OR environmentId + agentIdentifier) | - | -| `showTyping` | No | Show typing indicators | `true` | -| `headerBackgroundColor` | No | Header bar color (accepts any CSS color value) | `white` | -| `agentTitle` | No | Display title for the agent | `"Copilot Studio Agent"` | - -{: .note } -> You must provide either `directConnectUrl` OR both `environmentId` and `agentIdentifier`. - -### Step 5: Test Locally (Optional) - -Before building and deploying, you can test the extension locally: - -```bash -# Install dependencies -npm install - -# Check if gulp is installed, if not, install it -which gulp || npm install -g gulp-cli -# On Windows, use: where gulp || npm install -g gulp-cli - -# Serve the solution locally -gulp serve --nobrowser -``` - -After running `gulp serve`, navigate to the following URL (replace the values with your configuration): - -{% raw %} -``` -https://YOUR-TENANT.sharepoint.com/sites/YOUR-SITE/SitePages/Home.aspx?debugManifestsFile=https://localhost:4321/temp/build/manifests.js&loadSPFX=true&customActions={%224c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2%22:{%22location%22:%22ClientSideExtension.ApplicationCustomizer%22,%22properties%22:{%22appClientId%22:%22YOUR_APP_CLIENT_ID%22,%22tenantId%22:%22YOUR_TENANT_ID%22,%22directConnectUrl%22:%22YOUR_DIRECT_CONNECT_URL%22,%22showTyping%22:true,%22headerBackgroundColor%22:%22white%22,%22agentTitle%22:%22Your%20Agent%20Title%22}}} -``` -{% endraw %} - -- Replace `YOUR-TENANT`, `YOUR-SITE`, `YOUR_APP_CLIENT_ID`, `YOUR_TENANT_ID`, and `YOUR_DIRECT_CONNECT_URL` with your actual values -- Ensure you've added `https://localhost:4321` to your app registration's redirect URIs if testing locally - -### Step 6: Build and Package - -Once you've tested locally (or if you're ready to deploy directly): - -```bash -# Build the solution -gulp build - -# Bundle the solution -gulp bundle --ship - -# Package the solution -gulp package-solution --ship -``` - -This will create a `.sppkg` file in the `sharepoint/solution` folder. - -### Step 7: Deploy to SharePoint - -Upload the `.sppkg` file from `sharepoint/solution` to your SharePoint App Catalog. For detailed instructions on creating an app catalog, deploying apps, and adding them to sites, see [Add custom apps to SharePoint](https://learn.microsoft.com/en-us/sharepoint/use-app-catalog#add-custom-apps). - -## Troubleshooting - -### "Unable to acquire token" error - -If you encounter this error: -1. Open your browser's Developer Tools (F12) -2. Go to the **Network** tab -3. Look for failed authentication requests (usually to `login.microsoftonline.com`) -4. Click on the failed requests to see detailed error messages -5. Follow the authentication URLs that MSAL is attempting - the actual error details are often revealed in the response or redirect parameters - -Common causes: -- Missing or incorrect redirect URIs in your app registration (check for trailing slashes!) -- Incorrect permissions in the app registration (ensure `CopilotStudio.Copilots.Invoke` is granted) -- Incorrect tenant ID or client ID -- MSAL authentication flow being blocked by browser settings - -### "The redirect URI specified in the request does not match the redirect URIs configured for the application" error - -Check that the current SharePoint URL has been added as an allowed redirect URI for the provided app registration. - -## Additional Resources - -- [Built-in SharePoint Channel for Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-sharepoint#configure-the-sharepoint-channel) -- [Authenticate with Microsoft in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/configuration-end-user-authentication#authenticate-with-microsoft) -- [Tenant Graph Grounding in Copilot Studio](https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-copilot-studio#tenant-graph-grounding) -- [Microsoft 365 Agents SDK for NodeJS/TypeScript](https://github.com/microsoft/Agents-for-js) -- [BotFramework-WebChat with Fluent UI Theme](https://github.com/microsoft/BotFramework-WebChat#experimental-fluent-ui-theme-pack) -- [Copilot Studio Documentation](https://learn.microsoft.com/en-us/microsoft-copilot-studio/) -- [SharePoint Framework Documentation](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/sharepoint-framework-overview) - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/config.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/config.json deleted file mode 100644 index 64aac1c1..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", - "version": "2.0", - "bundles": { - "sidebar-agent-application-customizer": { - "components": [ - { - "entrypoint": "./lib/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.js", - "manifest": "./src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.manifest.json" - } - ] - } - }, - "externals": {}, - "localizedResources": { - "SidebarAgentApplicationCustomizerStrings": "lib/extensions/sidebarAgent/loc/{locale}.js" - } -} diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/deploy-azure-storage.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/deploy-azure-storage.json deleted file mode 100644 index a549ff80..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/deploy-azure-storage.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", - "workingDir": "./release/assets/", - "account": "", - "container": "sidebar-agent", - "accessKey": "" -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/package-solution.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/package-solution.json deleted file mode 100644 index a4a21d53..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/package-solution.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", - "solution": { - "name": "sidebar-agent-client-side-solution", - "id": "54cbba6a-2459-43a9-80a4-bc0675a81ac0", - "version": "1.0.0.0", - "includeClientSideAssets": true, - "skipFeatureDeployment": true, - "isDomainIsolated": false, - "developer": { - "name": "", - "websiteUrl": "", - "privacyUrl": "", - "termsOfUseUrl": "", - "mpnId": "Undefined-1.21.1" - }, - "metadata": { - "shortDescription": { - "default": "sidebar-agent description" - }, - "longDescription": { - "default": "sidebar-agent description" - }, - "screenshotPaths": [], - "videoUrl": "", - "categories": [] - }, - "features": [ - { - "title": "Application Extension - Deployment of custom action", - "description": "Deploys a custom action with ClientSideComponentId association", - "id": "f757a2ed-fea6-4478-abd2-080c4a1f6a84", - "version": "1.0.0.0", - "assets": { - "elementManifests": [ - "elements.xml", - "ClientSideInstance.xml" - ] - } - } - ] - }, - "paths": { - "zippedPackage": "solution/sidebar-agent.sppkg" - } -} diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/sass.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/sass.json deleted file mode 100644 index 5e78c982..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/sass.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/serve.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/serve.json deleted file mode 100644 index 0337190d..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/serve.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", - "port": 4321, - "https": true, - "serveConfigurations": { - "default": { - "pageUrl": "https://m365x66364744.sharepoint.com/sites/GlobalSales/SitePages/Home.aspx", - "customActions": { - "4c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2": { - "location": "ClientSideExtension.ApplicationCustomizer", - "properties": { - "appClientId": "APP-REGISTRATION-ID", - "tenantId": "TENANT-ID", - "environmentId": "ENV-ID", - "agentIdentifier": "AGENT-ID", - "directConnectUrl": "YOUR-DIRECT-CONNECT-URL", - "showTyping": true - } - } - } - }, - "sidebarAgent": { - "pageUrl": "https://m365x66364744.sharepoint.com/sites/GlobalSales/SitePages/Home.aspx", - "customActions": { - "4c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2": { - "location": "ClientSideExtension.ApplicationCustomizer", - "properties": { - "appClientId": "APP-REGISTRATION-ID", - "tenantId": "TENANT-ID", - "environmentId": "ENV-ID", - "agentIdentifier": "AGENT-ID", - "directConnectUrl": "YOUR-DIRECT-CONNECT-URL", - "showTyping": true - } - } - } - } - } -} diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/write-manifests.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/write-manifests.json deleted file mode 100644 index bad35260..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/config/write-manifests.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", - "cdnBasePath": "" -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/gulpfile.js b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/gulpfile.js deleted file mode 100644 index be291870..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/gulpfile.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const build = require('@microsoft/sp-build-web'); - -build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); - -var getTasks = build.rig.getTasks; -build.rig.getTasks = function () { - var result = getTasks.call(build.rig); - - result.set('serve', result.get('serve-deprecated')); - - return result; -}; - -build.initialize(require('gulp')); diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/package.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/package.json deleted file mode 100644 index 8fe1cf5c..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "sidebar-agent", - "version": "0.0.1", - "private": true, - "engines": { - "node": ">=22.14.0 < 23.0.0" - }, - "main": "lib/index.js", - "scripts": { - "build": "gulp bundle", - "clean": "gulp clean", - "test": "gulp test" - }, - "dependencies": { - "@azure/msal-browser": "^4.13.1", - "@fluentui/react": "^8.121.4", - "@microsoft/agents-copilotstudio-client": "^1.0.0", - "@microsoft/decorators": "1.21.1", - "@microsoft/sp-application-base": "1.21.1", - "@microsoft/sp-core-library": "1.21.1", - "botframework-webchat": "4.18.1-main.20250804.93043a9", - "botframework-webchat-fluent-theme": "4.18.1-main.20250804.93043a9", - "react": "17.0.1", - "react-dom": "17.0.1", - "tslib": "2.3.1" - }, - "devDependencies": { - "@microsoft/eslint-config-spfx": "1.21.1", - "@microsoft/eslint-plugin-spfx": "1.21.1", - "@microsoft/rush-stack-compiler-5.3": "0.1.0", - "@microsoft/sp-build-web": "1.21.1", - "@microsoft/sp-module-interfaces": "1.21.1", - "@rushstack/eslint-config": "4.0.1", - "@types/webpack-env": "~1.15.2", - "ajv": "^6.12.5", - "eslint": "8.57.1", - "gulp": "4.0.2", - "typescript": "~5.3.3" - } -} diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/sharepoint/assets/ClientSideInstance.xml b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/sharepoint/assets/ClientSideInstance.xml deleted file mode 100644 index 04b50040..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/sharepoint/assets/ClientSideInstance.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/sharepoint/assets/elements.xml b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/sharepoint/assets/elements.xml deleted file mode 100644 index 70f90775..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/sharepoint/assets/elements.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/index.ts b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/index.ts deleted file mode 100644 index fb81db1e..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -// A file is required to be in the root of the /src directory by the TypeScript compiler diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/tsconfig.json b/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/tsconfig.json deleted file mode 100644 index 419cd861..00000000 --- a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/tsconfig.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "extends": "./node_modules/@microsoft/rush-stack-compiler-5.3/includes/tsconfig-web.json", - "compilerOptions": { - "target": "es5", - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "jsx": "react", - "declaration": true, - "sourceMap": true, - "experimentalDecorators": true, - "skipLibCheck": true, - "outDir": "lib", - "inlineSources": false, - "noImplicitAny": true, - - "typeRoots": [ - "./node_modules/@types", - "./node_modules/@microsoft" - ], - "types": [ - "webpack-env" - ], - "lib": [ - "es5", - "dom", - "es2015.collection", - "es2015.promise" - ] - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx" - ] -} diff --git a/ui/embed/sharepoint-customizer/config/config.json b/ui/embed/sharepoint-customizer/config/config.json index 18435363..64aac1c1 100644 --- a/ui/embed/sharepoint-customizer/config/config.json +++ b/ui/embed/sharepoint-customizer/config/config.json @@ -2,17 +2,17 @@ "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", "version": "2.0", "bundles": { - "pva-sso-application-customizer": { + "sidebar-agent-application-customizer": { "components": [ { - "entrypoint": "./lib/extensions/pvaSso/PvaSsoApplicationCustomizer.js", - "manifest": "./src/extensions/pvaSso/PvaSsoApplicationCustomizer.manifest.json" + "entrypoint": "./lib/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.js", + "manifest": "./src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.manifest.json" } ] } }, "externals": {}, "localizedResources": { - "PvaSsoApplicationCustomizerStrings": "lib/extensions/pvaSso/loc/{locale}.js" + "SidebarAgentApplicationCustomizerStrings": "lib/extensions/sidebarAgent/loc/{locale}.js" } } diff --git a/ui/embed/sharepoint-customizer/config/deploy-azure-storage.json b/ui/embed/sharepoint-customizer/config/deploy-azure-storage.json index 9749f859..a549ff80 100644 --- a/ui/embed/sharepoint-customizer/config/deploy-azure-storage.json +++ b/ui/embed/sharepoint-customizer/config/deploy-azure-storage.json @@ -2,6 +2,6 @@ "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", "workingDir": "./release/assets/", "account": "", - "container": "pva-extension-sso", + "container": "sidebar-agent", "accessKey": "" } \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/config/package-solution.json b/ui/embed/sharepoint-customizer/config/package-solution.json index f17b9eca..a4a21d53 100644 --- a/ui/embed/sharepoint-customizer/config/package-solution.json +++ b/ui/embed/sharepoint-customizer/config/package-solution.json @@ -1,8 +1,8 @@ { "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", "solution": { - "name": "pva-extension-sso-client-side-solution", - "id": "14634225-91e5-41a4-b9cc-161ccb3400b4", + "name": "sidebar-agent-client-side-solution", + "id": "54cbba6a-2459-43a9-80a4-bc0675a81ac0", "version": "1.0.0.0", "includeClientSideAssets": true, "skipFeatureDeployment": true, @@ -12,14 +12,14 @@ "websiteUrl": "", "privacyUrl": "", "termsOfUseUrl": "", - "mpnId": "Undefined-1.18.0" + "mpnId": "Undefined-1.21.1" }, "metadata": { "shortDescription": { - "default": "pva-extension-sso description" + "default": "sidebar-agent description" }, "longDescription": { - "default": "pva-extension-sso description" + "default": "sidebar-agent description" }, "screenshotPaths": [], "videoUrl": "", @@ -29,7 +29,7 @@ { "title": "Application Extension - Deployment of custom action", "description": "Deploys a custom action with ClientSideComponentId association", - "id": "b9966a99-b9c1-4136-a847-d3e236c784a0", + "id": "f757a2ed-fea6-4478-abd2-080c4a1f6a84", "version": "1.0.0.0", "assets": { "elementManifests": [ @@ -41,6 +41,6 @@ ] }, "paths": { - "zippedPackage": "solution/pva-extension-sso.sppkg" + "zippedPackage": "solution/sidebar-agent.sppkg" } } diff --git a/ui/embed/sharepoint-customizer/config/serve.json b/ui/embed/sharepoint-customizer/config/serve.json index 3f996aaa..0337190d 100644 --- a/ui/embed/sharepoint-customizer/config/serve.json +++ b/ui/embed/sharepoint-customizer/config/serve.json @@ -4,31 +4,33 @@ "https": true, "serveConfigurations": { "default": { - "pageUrl": "YOUR_SHAREPOINT_SITE", + "pageUrl": "https://m365x66364744.sharepoint.com/sites/GlobalSales/SitePages/Home.aspx", "customActions": { - "bbcf8287-ea2d-4bb6-868f-19b9cf4b0812": { + "4c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2": { "location": "ClientSideExtension.ApplicationCustomizer", "properties": { - "botURL": "YOUR_BOT_URL", - "customScope" : "YOUR_CUSTOM_SCOPE", - "clientID" : "YOUR_CLIENT_ID", - "authority" : "YOUR_TENANT", - "greet" : true + "appClientId": "APP-REGISTRATION-ID", + "tenantId": "TENANT-ID", + "environmentId": "ENV-ID", + "agentIdentifier": "AGENT-ID", + "directConnectUrl": "YOUR-DIRECT-CONNECT-URL", + "showTyping": true } } } }, - "pvaSso": { - "pageUrl": "YOUR_SHAREPOINT_SITE", + "sidebarAgent": { + "pageUrl": "https://m365x66364744.sharepoint.com/sites/GlobalSales/SitePages/Home.aspx", "customActions": { - "bbcf8287-ea2d-4bb6-868f-19b9cf4b0812": { + "4c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2": { "location": "ClientSideExtension.ApplicationCustomizer", "properties": { - "botURL": "YOUR_BOT_URL", - "customScope" : "YOUR_CUSTOM_SCOPE", - "clientID" : "YOUR_CLIENT_ID", - "authority" : "YOUR_TENANT", - "greet" : true + "appClientId": "APP-REGISTRATION-ID", + "tenantId": "TENANT-ID", + "environmentId": "ENV-ID", + "agentIdentifier": "AGENT-ID", + "directConnectUrl": "YOUR-DIRECT-CONNECT-URL", + "showTyping": true } } } diff --git a/ui/embed/sharepoint-customizer/gulpfile.js b/ui/embed/sharepoint-customizer/gulpfile.js index 4312f1ff..be291870 100644 --- a/ui/embed/sharepoint-customizer/gulpfile.js +++ b/ui/embed/sharepoint-customizer/gulpfile.js @@ -13,23 +13,4 @@ build.rig.getTasks = function () { return result; }; -build.configureWebpack.mergeConfig({ - additionalConfiguration: (generatedConfiguration) => { - generatedConfiguration.module.rules.push( - { - test: /\.js$/, - exclude: /node_modules\/(?!htmlparser2)/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'] - } - } - } - ); - - return generatedConfiguration; - } -}); - build.initialize(require('gulp')); diff --git a/ui/embed/sharepoint-customizer/image.png b/ui/embed/sharepoint-customizer/image.png deleted file mode 100644 index a95c41be..00000000 Binary files a/ui/embed/sharepoint-customizer/image.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/SharePointSSOComponent.png b/ui/embed/sharepoint-customizer/images/SharePointSSOComponent.png deleted file mode 100644 index 44efd097..00000000 Binary files a/ui/embed/sharepoint-customizer/images/SharePointSSOComponent.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/images/agent sidepanel.png b/ui/embed/sharepoint-customizer/images/agent sidepanel.png similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/images/agent sidepanel.png rename to ui/embed/sharepoint-customizer/images/agent sidepanel.png diff --git a/ui/embed/sharepoint-customizer/images/apiPermissions.png b/ui/embed/sharepoint-customizer/images/apiPermissions.png deleted file mode 100644 index 23314c73..00000000 Binary files a/ui/embed/sharepoint-customizer/images/apiPermissions.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/apisMyOrganization.png b/ui/embed/sharepoint-customizer/images/apisMyOrganization.png deleted file mode 100644 index 6e7cd424..00000000 Binary files a/ui/embed/sharepoint-customizer/images/apisMyOrganization.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/clientID.png b/ui/embed/sharepoint-customizer/images/clientID.png deleted file mode 100644 index 6d1c93fc..00000000 Binary files a/ui/embed/sharepoint-customizer/images/clientID.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/customScope.png b/ui/embed/sharepoint-customizer/images/customScope.png deleted file mode 100644 index 402aa065..00000000 Binary files a/ui/embed/sharepoint-customizer/images/customScope.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/folderStructure.png b/ui/embed/sharepoint-customizer/images/folderStructure.png deleted file mode 100644 index 1553cdae..00000000 Binary files a/ui/embed/sharepoint-customizer/images/folderStructure.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/scopePermissions.png b/ui/embed/sharepoint-customizer/images/scopePermissions.png deleted file mode 100644 index bfde64d2..00000000 Binary files a/ui/embed/sharepoint-customizer/images/scopePermissions.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/images/toeknExchangeURL.png b/ui/embed/sharepoint-customizer/images/toeknExchangeURL.png deleted file mode 100644 index 7d8c3f37..00000000 Binary files a/ui/embed/sharepoint-customizer/images/toeknExchangeURL.png and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/package.json b/ui/embed/sharepoint-customizer/package.json index 48dfede8..8fe1cf5c 100644 --- a/ui/embed/sharepoint-customizer/package.json +++ b/ui/embed/sharepoint-customizer/package.json @@ -1,9 +1,9 @@ { - "name": "pva-extension-sso", + "name": "sidebar-agent", "version": "0.0.1", "private": true, "engines": { - "node": ">=16.13.0 <17.0.0 || >=18.17.1 <19.0.0" + "node": ">=22.14.0 < 23.0.0" }, "main": "lib/index.js", "scripts": { @@ -12,32 +12,29 @@ "test": "gulp test" }, "dependencies": { - "@microsoft/decorators": "1.18.0", - "@microsoft/sp-application-base": "1.18.0", - "@microsoft/sp-core-library": "1.18.0", - "@microsoft/sp-dialog": "1.18.0", - "@uifabric/react-hooks": "^7.16.4", - "botframework-webchat": "^4.15.9", - "office-ui-fabric-react": "^7.204.0", - "p-defer-es5": "^2.0.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "@azure/msal-browser": "^4.13.1", + "@fluentui/react": "^8.121.4", + "@microsoft/agents-copilotstudio-client": "^1.0.0", + "@microsoft/decorators": "1.21.1", + "@microsoft/sp-application-base": "1.21.1", + "@microsoft/sp-core-library": "1.21.1", + "botframework-webchat": "4.18.1-main.20250804.93043a9", + "botframework-webchat-fluent-theme": "4.18.1-main.20250804.93043a9", + "react": "17.0.1", + "react-dom": "17.0.1", "tslib": "2.3.1" }, "devDependencies": { - "@microsoft/eslint-config-spfx": "1.18.0", - "@microsoft/eslint-plugin-spfx": "1.18.0", - "@microsoft/rush-stack-compiler-4.7": "0.1.0", - "@microsoft/sp-build-web": "1.18.0", - "@microsoft/sp-module-interfaces": "1.18.0", - "@rushstack/eslint-config": "2.5.1", + "@microsoft/eslint-config-spfx": "1.21.1", + "@microsoft/eslint-plugin-spfx": "1.21.1", + "@microsoft/rush-stack-compiler-5.3": "0.1.0", + "@microsoft/sp-build-web": "1.21.1", + "@microsoft/sp-module-interfaces": "1.21.1", + "@rushstack/eslint-config": "4.0.1", "@types/webpack-env": "~1.15.2", "ajv": "^6.12.5", - "eslint": "8.7.0", + "eslint": "8.57.1", "gulp": "4.0.2", - "typescript": "4.7.4", - "babel-loader": "^8.3.0", - "@babel/core": "^7.23.0", - "@babel/preset-env": "^7.22.20" + "typescript": "~5.3.3" } } diff --git a/ui/embed/sharepoint-customizer/populate_elements_xml.py b/ui/embed/sharepoint-customizer/populate_elements_xml.py deleted file mode 100644 index 806576fb..00000000 --- a/ui/embed/sharepoint-customizer/populate_elements_xml.py +++ /dev/null @@ -1,84 +0,0 @@ -import xml.etree.ElementTree as ET -import json -import re - -# Define the namespace -ns = {'sp': 'http://schemas.microsoft.com/sharepoint/'} - -def get_user_input(key, value_type, current_value): - if value_type == bool: - while True: - user_input = input(f"Enter new value for '{key}' boolean ('true' or 'false', current: {current_value}): ").strip().lower() - if user_input in ['true', 'false']: - return user_input == 'true' - print("Invalid input for boolean, please enter 'true' or 'false'.") - else: - return input(f"Enter new value for '{key}' {value_type} (current: {current_value}): ").strip() - -def parse_properties(properties_str): - # Replace placeholder boolean value with an actual boolean for parsing - properties_str = properties_str.replace("TRUE_OR_FALSE", "true") # Assuming default as true - return json.loads(properties_str) - -def update_properties(properties): - new_properties = {} - for key, value in properties.items(): - value_type = bool if isinstance(value, bool) else str - new_properties[key] = get_user_input(key, value_type, value) - return new_properties - -def escape_json_for_xml(json_obj): - # Dump the JSON object to a string with double quotes, and without spaces after separators - json_str = json.dumps(json_obj, separators=(',', ':')) - # Replace double quotes with the XML escape sequence for a quote - escaped_json_str = json_str.replace('"', '"') - return escaped_json_str - -def update_xml(file_path, new_properties): - tree = ET.parse(file_path) - root = tree.getroot() - - # Set the default namespace for the XML file - ET.register_namespace('', 'http://schemas.microsoft.com/sharepoint/') - - # Convert our properties to the correctly escaped string for XML - escaped_properties_str = escape_json_for_xml(new_properties) - - # Find the correct CustomAction element and update it - for custom_action in root.findall(".//{http://schemas.microsoft.com/sharepoint/}CustomAction[@ClientSideComponentProperties]"): - # Set the escaped string directly, avoiding further XML escaping - custom_action.set('ClientSideComponentProperties', escaped_properties_str) - - # Write the updated XML to a string - xml_str = ET.tostring(root, encoding='unicode') - - # Replace the namespace prefixes that ElementTree adds to the tags - xml_str = re.sub(r' xmlns:ns0="[^"]+"', '', xml_str, count=1) # Remove the xmlns attribute - xml_str = xml_str.replace('ns0:', '') # Remove the ns0 prefix - - # Correct the ampersand escaping issue - xml_str = xml_str.replace('&quot;', '"') - - # Write the corrected XML string to the file - with open(file_path, 'w', encoding='utf-8') as file: - file.write('\n') # Write the XML declaration - file.write(xml_str) - -# Use the provided file path -file_path = 'sharepoint/assets/elements.xml' -tree = ET.parse(file_path) -root = tree.getroot() - -# Include the namespace when finding the tag -for custom_action in root.findall("sp:CustomAction[@ClientSideComponentProperties]", ns): - properties_str = custom_action.get('ClientSideComponentProperties') - - # Check if properties_str is not None or empty - if properties_str: - properties = parse_properties(properties_str) - new_properties = update_properties(properties) - update_xml(file_path, new_properties) - - print("XML file has been updated with new properties.") - else: - print("No ClientSideComponentProperties attribute found.") \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/sharepoint/assets/ClientSideInstance.xml b/ui/embed/sharepoint-customizer/sharepoint/assets/ClientSideInstance.xml index 77f1638a..04b50040 100644 --- a/ui/embed/sharepoint-customizer/sharepoint/assets/ClientSideInstance.xml +++ b/ui/embed/sharepoint-customizer/sharepoint/assets/ClientSideInstance.xml @@ -1,9 +1,9 @@ + ComponentId="4c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2" + Properties=""> \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/sharepoint/assets/elements.xml b/ui/embed/sharepoint-customizer/sharepoint/assets/elements.xml index 57cbd0bd..70f90775 100644 --- a/ui/embed/sharepoint-customizer/sharepoint/assets/elements.xml +++ b/ui/embed/sharepoint-customizer/sharepoint/assets/elements.xml @@ -1,9 +1,18 @@ + ClientSideComponentId="4c6e29f2-7eee-4f9f-bbd2-20c8859d0ba2" + ClientSideComponentProperties="{ + "appClientId": "YOUR_APP_CLIENT_ID", + "tenantId": "YOUR_TENANT_ID", + "environmentId": "YOUR_ENVIRONMENT_ID", + "agentIdentifier": "YOUR_AGENT_IDENTIFIER", + "directConnectUrl": "YOUR_DIRECT_CONNECT_URL", + "showTyping": true, + "headerBackgroundColor": "white", + "agentTitle": "Copilot Studio Agent" + }"> \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/sharepoint/solution/pva-extension-sso.sppkg b/ui/embed/sharepoint-customizer/sharepoint/solution/pva-extension-sso.sppkg deleted file mode 100644 index bd6a8b10..00000000 Binary files a/ui/embed/sharepoint-customizer/sharepoint/solution/pva-extension-sso.sppkg and /dev/null differ diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/PvaSsoApplicationCustomizer.manifest.json b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/PvaSsoApplicationCustomizer.manifest.json deleted file mode 100644 index 66679957..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/PvaSsoApplicationCustomizer.manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-extension-manifest.schema.json", - - "id": "bbcf8287-ea2d-4bb6-868f-19b9cf4b0812", - "alias": "PvaSsoApplicationCustomizer", - "componentType": "Extension", - "extensionType": "ApplicationCustomizer", - - // The "*" signifies that the version should be taken from the package.json - "version": "*", - "manifestVersion": 2, - - // If true, the component can only be installed on sites where Custom Script is allowed. - // Components that allow authors to embed arbitrary script code should set this to true. - // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f - "requiresCustomScript": false -} diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/PvaSsoApplicationCustomizer.ts b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/PvaSsoApplicationCustomizer.ts deleted file mode 100644 index d68bd81b..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/PvaSsoApplicationCustomizer.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Log } from '@microsoft/sp-core-library'; -import { - BaseApplicationCustomizer, - PlaceholderContent, - PlaceholderName -} from '@microsoft/sp-application-base'; -//import { Dialog } from '@microsoft/sp-dialog'; -import * as ReactDOM from "react-dom"; -import * as React from "react"; -import Chatbot from './components/ChatBot'; - - - -import * as strings from 'PvaSsoApplicationCustomizerStrings'; - - -import { override } from '@microsoft/decorators'; -import { IChatbotProps } from './components/IChatBotProps'; - -const LOG_SOURCE: string = 'PvaSsoApplicationCustomizer'; - -/** - * If your command set uses the ClientSideComponentProperties JSON input, - * it will be deserialized into the BaseExtension.properties object. - * You can define an interface to describe it. - */ -/** - * Properties for the PvaSsoApplicationCustomizer. - */ -export interface IPvaSsoApplicationCustomizerProperties { - /** - * The URL of the bot. - */ - botURL: string; - /** - * The name of the bot. - */ - botName?: string; - /** - * The label for the button. - */ - buttonLabel?: string; - /** - * The email of the user. - */ - userEmail: string; - /** - * The URL of the bot's avatar image. - */ - botAvatarImage?: string; - /** - * The initials of the bot's avatar. - */ - botAvatarInitials?: string; - /** - * Whether or not to greet the user. - */ - greet?: boolean; - /** - * The custom scope defined in the Azure AD app registration for the bot. - */ - customScope: string; - /** - * The client ID from the Azure AD app registration for the bot. - */ - clientID: string; - /** - * Azure AD tenant login URL - */ - authority: string; -} - -/** A Custom Action which can be run during execution of a Client Side Application */ -export default class PvaSsoApplicationCustomizer - extends BaseApplicationCustomizer { - - private _bottomPlaceholder: PlaceholderContent | undefined; - - - @override - public onInit(): Promise { - - Log.info(LOG_SOURCE, `Bot URL ${this.properties.botURL}`); - - if (!this.properties.buttonLabel || this.properties.buttonLabel === "") { - this.properties.buttonLabel = strings.DefaultButtonLabel; - } - - if (!this.properties.botName || this.properties.botName === "") { - this.properties.botName = strings.DefaultBotName; - } - - if (this.properties.greet !== true) { - this.properties.greet = false; - } - - this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders); - - return Promise.resolve(); - } - - private _renderPlaceHolders(): void { - // Handling the bottom placeholder - if (!this._bottomPlaceholder) { - this._bottomPlaceholder = this.context.placeholderProvider.tryCreateContent( - PlaceholderName.Bottom, - { onDispose: this._onDispose } - ); - - // The extension should not assume that the expected placeholder is available. - if (!this._bottomPlaceholder) { - console.error("The expected placeholder (Bottom) was not found."); - return; - } - const user = this.context.pageContext.user; - const elem: React.ReactElement = React.createElement(Chatbot, { ...this.properties, userEmail: user.email, userFriendlyName: user.displayName }); - ReactDOM.render(elem, this._bottomPlaceholder.domElement); - } - } - - private _onDispose(): void { - } - -} diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/ChatBot.tsx b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/ChatBot.tsx deleted file mode 100644 index a79d4070..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/ChatBot.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import * as React from "react"; -import { useBoolean, useId } from '@uifabric/react-hooks'; -import * as ReactWebChat from 'botframework-webchat'; -import { Dialog, DialogType } from 'office-ui-fabric-react/lib/Dialog'; -import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; -import { Spinner } from 'office-ui-fabric-react/lib/Spinner'; -import { Dispatch } from 'redux' -import { useRef } from "react"; - -import { IChatbotProps } from "./IChatBotProps"; -import MSALWrapper from "./MSALWrapper"; - -export const PVAChatbotDialog: React.FunctionComponent = (props) => { - - // Dialog properties and states - const dialogContentProps = { - type: DialogType.normal, - title: props.botName, - closeButtonAriaLabel: 'Close' - }; - - const [hideDialog, { toggle: toggleHideDialog }] = useBoolean(true); - const labelId: string = useId('dialogLabel'); - const subTextId: string = useId('subTextLabel'); - - const modalProps = React.useMemo( - () => ({ - isBlocking: false, - }), - [labelId, subTextId], - ); - - // Your bot's token endpoint - const botURL = props.botURL; - - // constructing URL using regional settings - const environmentEndPoint = botURL.slice(0,botURL.indexOf('/powervirtualagents')); - const apiVersion = botURL.slice(botURL.indexOf('api-version')).split('=')[1]; - const regionalChannelSettingsURL = `${environmentEndPoint}/powervirtualagents/regionalchannelsettings?api-version=${apiVersion}`; - - // Using refs instead of IDs to get the webchat and loading spinner elements - const webChatRef = useRef(null); - const loadingSpinnerRef = useRef(null); - - // A utility function that extracts the OAuthCard resource URI from the incoming activity or return undefined - function getOAuthCardResourceUri(activity: any): string | undefined { - const attachment = activity?.attachments?.[0]; - if (attachment?.contentType === 'application/vnd.microsoft.card.oauth' && attachment.content.tokenExchangeResource) { - return attachment.content.tokenExchangeResource.uri; - } - } - - const handleLayerDidMount = async () => { - - const MSALWrapperInstance = new MSALWrapper(props.clientID, props.authority); - - // Trying to get token if user is already signed-in - let responseToken = await MSALWrapperInstance.handleLoggedInUser([props.customScope], props.userEmail); - - if (!responseToken) { - // Trying to get token if user is not signed-in - responseToken = await MSALWrapperInstance.acquireAccessToken([props.customScope], props.userEmail); - } - - const token = responseToken?.accessToken || null; - - // Get the regional channel URL - let regionalChannelURL; - - const regionalResponse = await fetch(regionalChannelSettingsURL); - if(regionalResponse.ok){ - const data = await regionalResponse.json(); - regionalChannelURL = data.channelUrlsById.directline; - } - else { - console.error(`HTTP error! Status: ${regionalResponse.status}`); - } - - - // Create DirectLine object - let directline: any; - - const response = await fetch(botURL); - - if (response.ok) { - const conversationInfo = await response.json(); - directline = ReactWebChat.createDirectLine({ - token: conversationInfo.token, - domain: regionalChannelURL + 'v3/directline', - }); - } else { - console.error(`HTTP error! Status: ${response.status}`); - } - - const store = ReactWebChat.createStore( - {}, - ({ dispatch }: { dispatch: Dispatch }) => (next: any) => (action: any) => { - - // Checking whether we should greet the user - if (props.greet) - { - if (action.type === "DIRECT_LINE/CONNECT_FULFILLED") { - console.log("Action:" + action.type); - dispatch({ - meta: { - method: "keyboard", - }, - payload: { - activity: { - channelData: { - postBack: true, - }, - //Web Chat will show the 'Greeting' System Topic message which has a trigger-phrase 'hello' - name: 'startConversation', - type: "event" - }, - }, - type: "DIRECT_LINE/POST_ACTIVITY", - }); - return next(action); - } - } - - // Checking whether the bot is asking for authentication - if (action.type === "DIRECT_LINE/INCOMING_ACTIVITY") { - const activity = action.payload.activity; - if (activity.from && activity.from.role === 'bot' && - (getOAuthCardResourceUri(activity))){ - directline.postActivity({ - type: 'invoke', - name: 'signin/tokenExchange', - value: { - id: activity.attachments[0].content.tokenExchangeResource.id, - connectionName: activity.attachments[0].content.connectionName, - token - }, - "from": { - id: props.userEmail, - name: props.userFriendlyName, - role: "user" - } - }).subscribe( - (id: any) => { - if(id === "retry"){ - // bot was not able to handle the invoke, so display the oauthCard (manual authentication) - console.log("bot was not able to handle the invoke, so display the oauthCard") - return next(action); - } - }, - (error: any) => { - // an error occurred to display the oauthCard (manual authentication) - console.log("An error occurred so display the oauthCard"); - return next(action); - } - ) - // token exchange was successful, do not show OAuthCard - return; - } - } else { - return next(action); - } - - return next(action); - } - ); - - // hide the upload button - other style options can be added here - const canvasStyleOptions = { - hideUploadButton: true - } - - // Render webchat - if (token && directline) { - if (webChatRef.current && loadingSpinnerRef.current) { - webChatRef.current.style.minHeight = '50vh'; - loadingSpinnerRef.current.style.display = 'none'; - ReactWebChat.renderWebChat( - { - directLine: directline, - store: store, - styleOptions: canvasStyleOptions, - userID: props.userEmail, - }, - webChatRef.current - ); - } else { - console.error("Webchat or loading spinner not found"); - } - } - - }; - - return ( - <> - - -
-
-
-
-
- - - ); -}; - -export default class Chatbot extends React.Component { - constructor(props: IChatbotProps) { - super(props); - } - public render(): JSX.Element { - return ( -
- -
- ); - } -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/IChatBotProps.ts b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/IChatBotProps.ts deleted file mode 100644 index a5edc684..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/IChatBotProps.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface IChatbotProps { - botURL: string; - buttonLabel?: string; - botName?: string; - userEmail: string; - userFriendlyName: string; - botAvatarImage?: string; - botAvatarInitials?: string; - greet?: boolean; - customScope: string; - clientID: string; - authority: string; -} \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/MSALWrapper.ts b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/MSALWrapper.ts deleted file mode 100644 index 143266d7..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/components/MSALWrapper.ts +++ /dev/null @@ -1,83 +0,0 @@ -// MSALWrapper.ts -import { PublicClientApplication, AuthenticationResult, - Configuration, InteractionRequiredAuthError} from "@azure/msal-browser"; - -export class MSALWrapper { - private msalConfig: Configuration; - - private msalInstance: PublicClientApplication; - - constructor(clientId: string, authority: string) { - this.msalConfig = { - auth: { - clientId: clientId, - authority: authority, - }, - cache: { - cacheLocation: "localStorage", - }, - }; - - this.msalInstance = new PublicClientApplication(this.msalConfig); - } - - public async handleLoggedInUser(scopes: string[], userEmail: string): Promise { - - let userAccount = null; - const accounts = this.msalInstance.getAllAccounts(); - - if(accounts === null || accounts.length === 0) { - console.log("No users are signed in"); - return null; - } else if (accounts.length > 1) - { - userAccount = this.msalInstance.getAccountByUsername(userEmail); - } else { - userAccount = accounts[0]; - } - - if(userAccount !== null) { - const accessTokenRequest = { - scopes: scopes, - account: userAccount - }; - - return this.msalInstance.acquireTokenSilent(accessTokenRequest).then((response) => { - return response; - }).catch((errorinternal) => { - console.log(errorinternal); - return null; - }); - } - return null; - } - - - public async acquireAccessToken(scopes: string[], userEmail: string): Promise { - - - const accessTokenRequest = { - scopes: scopes, - loginHint: userEmail - } - - return this.msalInstance.ssoSilent(accessTokenRequest).then((response) => { - return response - }).catch((silentError) => { - console.log(silentError); - if (silentError instanceof InteractionRequiredAuthError) { - return this.msalInstance.loginPopup(accessTokenRequest).then((response) => { - return response; - } - ).catch((error) => { - console.log(error); - return null; - }); - } - return null; - }) -} - -} - -export default MSALWrapper; \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/loc/en-us.js b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/loc/en-us.js deleted file mode 100644 index 93e839ba..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/loc/en-us.js +++ /dev/null @@ -1,7 +0,0 @@ -define([], function() { - return { - "Title": "PvaSsoApplicationCustomizer", - "DefaultButtonLabel": "Chat Now", - "DefaultBotName" : "MCS SSO Sample" - } -}); \ No newline at end of file diff --git a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/loc/myStrings.d.ts b/ui/embed/sharepoint-customizer/src/extensions/pvaSso/loc/myStrings.d.ts deleted file mode 100644 index 8ef0b80b..00000000 --- a/ui/embed/sharepoint-customizer/src/extensions/pvaSso/loc/myStrings.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare interface IPvaSsoApplicationCustomizerStrings { - Title: string; - DefaultButtonLabel: string; - DefaultBotName: string; -} - -declare module 'PvaSsoApplicationCustomizerStrings' { - const strings: IPvaSsoApplicationCustomizerStrings; - export = strings; -} diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.manifest.json b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.manifest.json similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.manifest.json rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.manifest.json diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.tsx b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.tsx similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.tsx rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/SidebarAgentApplicationCustomizer.tsx diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/Chat/Chat.tsx b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/Chat/Chat.tsx similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/Chat/Chat.tsx rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/Chat/Chat.tsx diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/Chat/acquireToken.ts b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/Chat/acquireToken.ts similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/Chat/acquireToken.ts rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/Chat/acquireToken.ts diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.module.scss b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.module.scss similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.module.scss rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.module.scss diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.tsx b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.tsx similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.tsx rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/components/SidePanel/SidePanel.tsx diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/loc/en-us.js b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/loc/en-us.js similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/loc/en-us.js rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/loc/en-us.js diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/loc/myStrings.d.ts b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/loc/myStrings.d.ts similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/loc/myStrings.d.ts rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/loc/myStrings.d.ts diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/models/ISidebarAgentProperties.ts b/ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/models/ISidebarAgentProperties.ts similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/extensions/sidebarAgent/models/ISidebarAgentProperties.ts rename to ui/embed/sharepoint-customizer/src/extensions/sidebarAgent/models/ISidebarAgentProperties.ts diff --git a/ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/scss.d.ts b/ui/embed/sharepoint-customizer/src/scss.d.ts similarity index 100% rename from ui/embed/sharepoint-customizer/SharePointSSOAppCustomizer/src/scss.d.ts rename to ui/embed/sharepoint-customizer/src/scss.d.ts diff --git a/ui/embed/sharepoint-customizer/tsconfig.json b/ui/embed/sharepoint-customizer/tsconfig.json index c4cd392a..419cd861 100644 --- a/ui/embed/sharepoint-customizer/tsconfig.json +++ b/ui/embed/sharepoint-customizer/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json", + "extends": "./node_modules/@microsoft/rush-stack-compiler-5.3/includes/tsconfig-web.json", "compilerOptions": { "target": "es5", "forceConsistentCasingInFileNames": true,