Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for MS Teams in GitHub Copilot /manage chat command #389

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "viva-connections-toolkit",
"displayName": "SharePoint Framework Toolkit",
"description": "SharePoint Framework Toolkit aims to boost your productivity in developing and managing SharePoint Framework solutions helping at every stage of your development flow, from setting up your development workspace to deploying a solution straight to your tenant without the need to leave VS Code and now even create a CI/CD pipeline to introduce automate deployment of your app. This toolkit is provided by the community.",
"version": "4.3.0",
"version": "4.3.1",
"publisher": "m365pnp",
"preview": false,
"homepage": "https://github.com/pnp/vscode-viva",
Expand Down Expand Up @@ -466,6 +466,11 @@
"category": "SharePoint Framework Toolkit",
"icon": "$(sync)"
},
{
"command": "spfx-toolkit.setFormCustomizer",
"title": "Set Form Customizer",
"category": "SharePoint Framework Toolkit"
},
{
"command": "spfx-toolkit.showMoreActions",
"title": "...",
Expand Down
4 changes: 3 additions & 1 deletion scripts/cli-for-microsoft365-create-grounding-data.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ if (-not (Test-Path -Path $cliLocalProjectPath -PathType Container)) {
[hashtable]$commandsData = @{}

$allSpoCommands = Get-ChildItem -Path "$workspacePath\cli-microsoft365\docs\docs\cmd\spo\*.mdx" -Recurse -Force -Exclude "_global*"
$allTeamsCommands = Get-ChildItem -Path "$workspacePath\cli-microsoft365\docs\docs\cmd\teams\*.mdx" -Recurse -Force -Exclude "_global*"
$allCommands = $allSpoCommands + $allTeamsCommands

foreach ($command in $allSpoCommands) {
foreach ($command in $allCommands) {
$commandDocs = ConvertFrom-Markdown -Path $command
$html = New-Object -Com 'HTMLFile'
$html.write([ref]$commandDocs.Html)
Expand Down
2,876 changes: 1,641 additions & 1,235 deletions src/chat/CliForMicrosoft365SpoCommands.ts

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions src/chat/PromptHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,14 @@ export class PromptHandlers {
}
} catch (err: any) {
Logger.getInstance();
Logger.error(err!.error ? err!.error.message.toString() : err.toString());
const errorText = err!.error ? err!.error.message.toString() : err.toString();
Logger.error(errorText);
stream.markdown('\n\nI was not able to retrieve the data from SharePoint. Please check the logs in output window for more information.');

const markdownString = new vscode.MarkdownString();
markdownString.supportHtml = true;
markdownString.appendMarkdown(`<span style="color:#f00;">${errorText}</span>`);
stream.markdown(markdownString);
}
}

Expand All @@ -86,7 +92,7 @@ export class PromptHandlers {
}

private static async tryToGetDataFromSharePoint(chatResponse: string): Promise<string | undefined> {
const cliRegex = /```([^\n]*)\n(?=[\s\S]*?m365 spo.+)([\s\S]*?)\n?```/g;
const cliRegex = /```([^\n]*)\n(?=[\s\S]*?m365.+)([\s\S]*?)\n?```/g;
const cliMatch = cliRegex.exec(chatResponse);

if (cliMatch && cliMatch[2]) {
Expand Down Expand Up @@ -127,6 +133,9 @@ export class PromptHandlers {
if (EnvironmentInformation.tenantUrl) {
context += `Tenant SharePoint URL is: ${EnvironmentInformation.tenantUrl}`;
}
if (EnvironmentInformation.account) {
context += `Current userName (account) is: ${EnvironmentInformation.account}`;
}
default:
context += promptGeneralContext;
}
Expand Down
5 changes: 4 additions & 1 deletion src/constants/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,8 @@ export const Commands = {
enableAppCatalogApp: `${EXTENSION_NAME}.enableAppCatalogApp`,
disableAppCatalogApp: `${EXTENSION_NAME}.disableAppCatalogApp`,
upgradeAppCatalogApp: `${EXTENSION_NAME}.upgradeAppCatalogApp`,
showMoreActions: `${EXTENSION_NAME}.showMoreActions`
showMoreActions: `${EXTENSION_NAME}.showMoreActions`,

// Set form customizer
setFormCustomizer: `${EXTENSION_NAME}.setFormCustomizer`
};
1 change: 1 addition & 0 deletions src/panels/CommandPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export class CommandPanel {

actionCommands.push(new ActionTreeItem('Add new component', '', { name: 'add', custom: false }, undefined, Commands.addToProject));
actionCommands.push(new ActionTreeItem('Scaffold CI/CD Workflow', '', { name: 'rocket', custom: false }, undefined, Commands.pipeline));
actionCommands.push(new ActionTreeItem('Set Form Customizer', '', { name: 'checklist', custom: false }, undefined, Commands.setFormCustomizer));
actionCommands.push(new ActionTreeItem('View samples', '', { name: 'library', custom: false }, undefined, Commands.samplesGallery));

window.registerTreeDataProvider('pnp-view-actions', new ActionTreeDataProvider(actionCommands));
Expand Down
122 changes: 116 additions & 6 deletions src/services/actions/CliActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export class CliActions {
subscriptions.push(
commands.registerCommand(Commands.upgradeAppCatalogApp, CliActions.upgradeAppCatalogApp)
);
subscriptions.push(
commands.registerCommand(Commands.setFormCustomizer, CliActions.setFormCustomizer)
);
}

/**
Expand Down Expand Up @@ -440,7 +443,7 @@ export class CliActions {

await window.withProgress({
location: ProgressLocation.Notification,
title: 'Creating app registration...',
title: `Creating app registration... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
Expand Down Expand Up @@ -478,7 +481,7 @@ export class CliActions {

await window.withProgress({
location: ProgressLocation.Notification,
title: `Generating ${input.workflowType === WorkflowType.gitHub ? 'GitHub Workflow' : 'Azure DevOps Pipeline'}...`,
title: `Generating ${input.workflowType === WorkflowType.gitHub ? 'GitHub Workflow' : 'Azure DevOps Pipeline'}... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
Expand Down Expand Up @@ -572,7 +575,7 @@ export class CliActions {

await window.withProgress({
location: ProgressLocation.Notification,
title: 'Generating the upgrade steps...',
title: `Generating the upgrade steps... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
Expand Down Expand Up @@ -638,7 +641,7 @@ export class CliActions {

await window.withProgress({
location: ProgressLocation.Notification,
title: 'Renaming the current project...',
title: `Renaming the current project... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
Expand Down Expand Up @@ -690,7 +693,7 @@ export class CliActions {

await window.withProgress({
location: ProgressLocation.Notification,
title: 'Granting API permissions for the current project...',
title: `Granting API permissions for the current project... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
Expand Down Expand Up @@ -746,7 +749,7 @@ export class CliActions {

await window.withProgress({
location: ProgressLocation.Notification,
title: 'Validating the current project...',
title: `Validating the current project... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
Expand Down Expand Up @@ -920,4 +923,111 @@ export class CliActions {
Notifications.error(`${fileName}.tour file not found in path ${path.join(wsFolder.uri.fsPath, '.tours')}. Cannot start Code Tour.`);
}
}

/**
* Sets the form customizer for a content type on a list.
*/
public static async setFormCustomizer() {
const relativeUrl = await window.showInputBox({
prompt: 'Enter the relative URL of the site',
ignoreFocusOut: true,
placeHolder: 'e.g., sites/sales',
validateInput: (input) => {
if (!input) {
return 'site URL is required';
}

const trimmedInput = input.trim();

if (trimmedInput.startsWith('https://')) {
return 'Please provide a relative URL, not an absolute URL.';
}
if (trimmedInput.startsWith('/')) {
return 'Please provide a relative URL without a leading slash.';
}

return undefined;
}
});

if (relativeUrl === undefined) {
Notifications.warning('No site URL provided. Setting form customizer aborted.');
return;
}

const siteUrl = `${EnvironmentInformation.tenantUrl}/${relativeUrl.trim()}`;

const listTitle = await window.showInputBox({
prompt: 'Enter the list title',
ignoreFocusOut: true,
validateInput: (value) => value ? undefined : 'List title is required'
});

if (!listTitle) {
Notifications.warning('No list title provided. Setting form customizer aborted.');
return;
}

const contentType = await window.showInputBox({
prompt: 'Enter the Content Type name',
ignoreFocusOut: true,
validateInput: (value) => value ? undefined : 'Content Type name is required'
});

if (!contentType) {
Notifications.warning('No content type name provided. Setting form customizer aborted.');
return;
}

const editFormClientSideComponentId = await window.showInputBox({
prompt: 'Enter the Edit form customizer ID (leave empty to skip)',
ignoreFocusOut: true
});

const newFormClientSideComponentId = await window.showInputBox({
prompt: 'Enter the New form customizer ID (leave empty to skip)',
ignoreFocusOut: true
});

const displayFormClientSideComponentId = await window.showInputBox({
prompt: 'Enter the View form customizer ID (leave empty to skip)',
ignoreFocusOut: true
});

const commandOptions: any = {
webUrl: siteUrl,
listTitle: listTitle,
name: contentType
};

if (editFormClientSideComponentId) {
commandOptions.EditFormClientSideComponentId = editFormClientSideComponentId;
}

if (newFormClientSideComponentId ) {
commandOptions.NewFormClientSideComponentId = newFormClientSideComponentId ;
}

if (displayFormClientSideComponentId) {
commandOptions.DisplayFormClientSideComponentId = displayFormClientSideComponentId;
}

await window.withProgress({
location: ProgressLocation.Notification,
title: `Setting form customizer... Check [output window](command:${Commands.showOutputChannel}) to follow the progress.`,
cancellable: true
}, async (progress: Progress<{ message?: string; increment?: number }>) => {
try {
const result = await CliExecuter.execute('spo contenttype set', 'json', commandOptions);
if (result.stderr) {
Notifications.error(result.stderr);
} else {
Notifications.info('Form customizer set successfully.');
}
} catch (e: any) {
const message = e?.error?.message;
Notifications.error(message);
}
});
}
}