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

Refactor action naming #294

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
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
6 changes: 3 additions & 3 deletions npm-shrinkwrap.json

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

14 changes: 13 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": "3.4.0",
"version": "3.4.2",
"publisher": "m365pnp",
"preview": false,
"homepage": "https://github.com/pnp/vscode-viva",
Expand Down Expand Up @@ -167,6 +167,18 @@
"none"
],
"description": "Choose your preferred Node.js version manager. Choose between `nvs`, `nvm`, or `none`."
},
"spfx-toolkit.showServiceIncidentList": {
"title": "Show service health incidents",
"type": "boolean",
"default": "true",
"description": "Show the service health incidents in the account view."
},
"spfx-toolkit.showTenantWideExtensions": {
"title": "Show tenant-wide extensions",
"type": "boolean",
"default": "true",
"description": "Show the tenant-wide extensions in the account view."
}
}
},
Expand Down
18 changes: 7 additions & 11 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PnPWebview } from './webview/PnPWebview';
import { CommandPanel } from './panels/CommandPanel';
import * as vscode from 'vscode';
import { workspace, window, ThemeIcon, commands } from 'vscode';
import { workspace, commands } from 'vscode';
import { PROJECT_FILE, Scaffolder } from './services/Scaffolder';
import { Extension } from './services/Extension';
import { Dependencies } from './services/Dependencies';
Expand Down Expand Up @@ -39,32 +39,28 @@ export async function activate(context: vscode.ExtensionContext) {
if (fileContents) {
unlinkSync(files[0].fsPath);

const terminal = window.createTerminal({
name: 'Installing dependencies',
iconPath: new ThemeIcon('cloud-download')
});
const terminalTitle = 'Installing dependencies';
const terminalIcon = 'cloud-download';

if (fileContents.indexOf(ProjectFileContent.init) > -1 || fileContents.indexOf(ProjectFileContent.initScenario) > -1) {
terminal.sendText('npm i');
await TerminalCommandExecuter.runCommand('npm i', [], terminalTitle, terminalIcon);
}

if (fileContents.indexOf(ProjectFileContent.initScenario) > -1) {
commands.executeCommand('codetour.startTour');
}

if (fileContents.indexOf(ProjectFileContent.installReusablePropertyPaneControls) > -1) {
terminal.sendText('npm install @pnp/spfx-property-controls --save --save-exact');
await TerminalCommandExecuter.runCommand('npm install @pnp/spfx-property-controls --save --save-exact', [], terminalTitle, terminalIcon);
}

if (fileContents.indexOf(ProjectFileContent.installReusableReactControls) > -1) {
terminal.sendText('npm install @pnp/spfx-controls-react --save --save-exact');
await TerminalCommandExecuter.runCommand('npm install @pnp/spfx-controls-react --save --save-exact', [], terminalTitle, terminalIcon);
}

if (fileContents.indexOf(ProjectFileContent.installPnPJs) > -1) {
terminal.sendText('npm install @pnp/sp @pnp/graph --save');
await TerminalCommandExecuter.runCommand('npm install @pnp/sp @pnp/graph --save', [], terminalTitle, terminalIcon);
}

terminal.show(true);
}
}
});
Expand Down
58 changes: 35 additions & 23 deletions src/panels/CommandPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TeamsToolkitIntegration } from '../services/TeamsToolkitIntegration';
import { AdaptiveCardCheck } from '../services/AdaptiveCardCheck';
import { Subscription } from '../models';
import { Extension } from '../services/Extension';
import { getExtensionSettings } from '../utils';


export class CommandPanel {
Expand Down Expand Up @@ -115,15 +116,18 @@ export class CommandPanel {
new ActionTreeItem(webApiPermissionManagementUrl.replace(`${adminOriginUrl}/_layouts/15/online/AdminHome.aspx#/`, '...'), '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse(webApiPermissionManagementUrl), 'sp-admin-api-url')
]));

const healthInfoList = await CliActions.getTenantHealthInfo();
if (healthInfoList)
{
const healthInfoItems: ActionTreeItem[] = [];
for (let i = 0; i < healthInfoList.length; i++) {
healthInfoItems.push(new ActionTreeItem(healthInfoList[i].Title, '', { name: 'm365-warning', custom: true } , undefined, 'vscode.open', Uri.parse(healthInfoList[i].Url), 'm365-health-service-url'));
}
if (healthInfoItems.length > 0) {
accountCommands[0].children.push(new ActionTreeItem('Service health incidents', '', { name: 'm365-health', custom: true }, undefined, undefined, undefined, undefined, healthInfoItems));
const showServiceIncidentList = getExtensionSettings('showServiceIncidentList', true);
if (showServiceIncidentList === true) {
const healthInfoList = await CliActions.getTenantHealthInfo();
if (healthInfoList?.some)
{
const healthInfoItems: ActionTreeItem[] = [];
for (let i = 0; i < healthInfoList.length; i++) {
healthInfoItems.push(new ActionTreeItem(healthInfoList[i].Title, '', { name: 'm365-warning', custom: true } , undefined, 'vscode.open', Uri.parse(healthInfoList[i].Url), 'm365-health-service-url'));
}
if (healthInfoItems.length > 0) {
accountCommands[0].children.push(new ActionTreeItem('Service health incidents', '', { name: 'm365-health', custom: true }, undefined, undefined, undefined, undefined, healthInfoItems));
}
}
}
}
Expand Down Expand Up @@ -155,21 +159,29 @@ export class CommandPanel {
const origin = new URL(tenantAppCatalogUrl).origin;
commands.executeCommand('setContext', ContextKeys.hasAppCatalog, true);

const tenantWideExtensions = await CliActions.getTenantWideExtensions(tenantAppCatalogUrl);
const tenantWideExtensionsList: ActionTreeItem[] = [];
if (tenantWideExtensions && tenantWideExtensions?.length > 0) {
tenantWideExtensions.forEach((extension) => {
tenantWideExtensionsList.push(new ActionTreeItem(extension.Title, '', { name: 'spo-app', custom: true }, undefined, 'vscode.open', Uri.parse(extension.Url), 'sp-app-catalog-tenant-wide-extensions-url'));
});
}

environmentCommands.push(
new ActionTreeItem('Tenant App Catalog', '', { name: 'spo-logo', custom: true }, undefined, undefined, undefined, undefined, [
new ActionTreeItem(tenantAppCatalogUrl.replace(origin, '...'), '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse(tenantAppCatalogUrl), 'sp-app-catalog-url'),
new ActionTreeItem('Tenant-wide Extensions', '', { name: 'spo-app-list', custom: true }, undefined, undefined, undefined, 'sp-app-catalog-tenant-wide-extensions', tenantWideExtensionsList)
new ActionTreeItem(tenantAppCatalogUrl.replace(origin, '...'), '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse(tenantAppCatalogUrl), 'sp-app-catalog-url')
]),
);

const showTenantWideExtensions = getExtensionSettings('showTenantWideExtensions', true);

if (showTenantWideExtensions === true) {
const tenantWideExtensions = await CliActions.getTenantWideExtensions(tenantAppCatalogUrl);
const tenantWideExtensionsList: ActionTreeItem[] = [];
if (tenantWideExtensions && tenantWideExtensions?.length > 0) {
tenantWideExtensions.forEach((extension) => {
tenantWideExtensionsList.push(new ActionTreeItem(extension.Title, '', { name: 'spo-app', custom: true }, undefined, 'vscode.open', Uri.parse(extension.Url), 'sp-app-catalog-tenant-wide-extensions-url'));
});
}
else {
tenantWideExtensionsList.push(new ActionTreeItem('none', '', undefined, undefined, undefined, undefined, undefined));
}

environmentCommands.push(new ActionTreeItem('Tenant-wide Extensions', '', { name: 'spo-app-list', custom: true }, undefined, undefined, undefined, 'sp-app-catalog-tenant-wide-extensions', tenantWideExtensionsList));
}

const siteAppCatalogActionItems: ActionTreeItem[] = [];
for (let i = 1; i < appCatalogUrls.length; i++) {
siteAppCatalogActionItems.push(new ActionTreeItem(appCatalogUrls[i].replace(origin, '...'), '', { name: 'globe', custom: false }, undefined, 'vscode.open', Uri.parse(appCatalogUrls[i]), 'sp-app-catalog-url'));
Expand Down Expand Up @@ -199,13 +211,13 @@ export class CommandPanel {

private static async actionsTreeView() {
const actionCommands: ActionTreeItem[] = [
new ActionTreeItem('Upgrade project', '', { name: 'arrow-up', custom: false }, undefined, Commands.upgradeProject),
new ActionTreeItem('Validate project', '', { name: 'check-all', custom: false }, undefined, Commands.validateProject),
new ActionTreeItem('Upgrade project SPFx version', '', { name: 'arrow-up', custom: false }, undefined, Commands.upgradeProject),
new ActionTreeItem('Validate project correctness', '', { name: 'check-all', custom: false }, undefined, Commands.validateProject),
new ActionTreeItem('Rename project', '', { name: 'whole-word', custom: false }, undefined, Commands.renameProject),
new ActionTreeItem('Grant API permissions', '', { name: 'workspace-trusted', custom: false }, undefined, Commands.grantAPIPermissions),
new ActionTreeItem('Deploy project (sppkg)', '', { name: 'cloud-upload', custom: false }, undefined, Commands.deployProject),
new ActionTreeItem('Deploy project to app catalog', '', { name: 'cloud-upload', custom: false }, undefined, Commands.deployProject),
new ActionTreeItem('Add new component', '', { name: 'add', custom: false }, undefined, Commands.addToProject),
new ActionTreeItem('CI/CD Workflow', '', { name: 'rocket', custom: false }, undefined, Commands.pipeline),
new ActionTreeItem('Scaffold CI/CD Workflow', '', { name: 'rocket', custom: false }, undefined, Commands.pipeline),
new ActionTreeItem('View samples', '', { name: 'library', custom: false }, undefined, Commands.samplesGallery),
];

Expand Down
84 changes: 48 additions & 36 deletions src/services/CliActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,25 @@ export class CliActions {
* @returns A promise that resolves to an array of app catalog URLs, or undefined if no app catalogs are found.
*/
public static async appCatalogUrlsGet(): Promise<string[] | undefined> {
const appCatalogUrls: string[] = [];
const tenantAppCatalog = (await CliExecuter.execute('spo tenant appcatalogurl get', 'json')).stdout || undefined;
const siteAppCatalogs = (await CliExecuter.execute('spo site appcatalog list', 'json')).stdout || undefined;
try {
const appCatalogUrls: string[] = [];
const tenantAppCatalog = (await CliExecuter.execute('spo tenant appcatalogurl get', 'json')).stdout || undefined;
const siteAppCatalogs = (await CliExecuter.execute('spo site appcatalog list', 'json')).stdout || undefined;

if (tenantAppCatalog) {
appCatalogUrls.push(JSON.parse(tenantAppCatalog));
}
if (tenantAppCatalog) {
appCatalogUrls.push(JSON.parse(tenantAppCatalog));
}

if (siteAppCatalogs) {
const siteAppCatalogsJson: SiteAppCatalog[] = JSON.parse(siteAppCatalogs);
siteAppCatalogsJson.forEach((siteAppCatalog) => appCatalogUrls.push(`${siteAppCatalog.AbsoluteUrl}/AppCatalog`));
}
if (siteAppCatalogs) {
const siteAppCatalogsJson: SiteAppCatalog[] = JSON.parse(siteAppCatalogs);
siteAppCatalogsJson.forEach((siteAppCatalog) => appCatalogUrls.push(`${siteAppCatalog.AbsoluteUrl}/AppCatalog`));
}

EnvironmentInformation.appCatalogUrls = appCatalogUrls ? appCatalogUrls : undefined;
return EnvironmentInformation.appCatalogUrls;
EnvironmentInformation.appCatalogUrls = appCatalogUrls ? appCatalogUrls : undefined;
return EnvironmentInformation.appCatalogUrls;
} catch {
return undefined;
}
}

/**
Expand All @@ -74,48 +78,56 @@ export class CliActions {
* @returns A promise that resolves to an array of objects containing the URL and title of each tenant-wide extension,
* or undefined if no extensions are found.
*/
public static async getTenantWideExtensions(tenantAppCatalogUrl: string): Promise<{Url: string, Title: string}[] | undefined> {
public static async getTenantWideExtensions(tenantAppCatalogUrl: string): Promise<{ Url: string, Title: string }[] | undefined> {
const origin = new URL(tenantAppCatalogUrl).origin;
const commandOptions: any = {
listUrl: `${tenantAppCatalogUrl.replace(origin, '')}/Lists/TenantWideExtensions`,
webUrl: tenantAppCatalogUrl
};
const tenantWideExtensions = (await CliExecuter.execute('spo listitem list', 'json', commandOptions)).stdout || undefined;
try {
const tenantWideExtensions = (await CliExecuter.execute('spo listitem list', 'json', commandOptions)).stdout || undefined;

if (!tenantWideExtensions) {
return undefined;
}

if (!tenantWideExtensions) {
const tenantWideExtensionsJson: any[] = JSON.parse(tenantWideExtensions);
const tenantWideExtensionList = tenantWideExtensionsJson.map((extension) => {
return {
Url: `${tenantAppCatalogUrl}/Lists/TenantWideExtensions/DispForm.aspx?ID=${extension.Id}`,
Title: extension.Title
};
});
return tenantWideExtensionList;
} catch {
return undefined;
}

const tenantWideExtensionsJson: any[] = JSON.parse(tenantWideExtensions);
const tenantWideExtensionList = tenantWideExtensionsJson.map((extension) => {
return {
Url: `${tenantAppCatalogUrl}/Lists/TenantWideExtensions/DispForm.aspx?ID=${extension.Id}`,
Title: extension.Title
};
});
return tenantWideExtensionList;
}

/**
* Retrieves the health information of the tenant services.
* @returns A promise that resolves to an array of objects containing the title and URL of the health information.
* Returns undefined if there is no health information available.
*/
public static async getTenantHealthInfo(): Promise<{Title: string, Url: string}[] | undefined> {
const healthInfo = (await CliExecuter.execute('tenant serviceannouncement health list', 'json')).stdout || undefined;
public static async getTenantHealthInfo(): Promise<{ Title: string, Url: string }[] | undefined> {
try {
const healthInfo = (await CliExecuter.execute('tenant serviceannouncement health list', 'json')).stdout || undefined;
if (!healthInfo) {
return undefined;
}

if (!healthInfo) {
const healthInfoJson: any[] = JSON.parse(healthInfo);
const healthInfoList = healthInfoJson.filter(service => service.status !== 'serviceOperational').map((service) => {
return {
Url: `https://admin.microsoft.com/#/servicehealth/:/currentIssues/${encodeURIComponent(service.service)}/`,
Title: service.service
};
});
return healthInfoList;
}
catch {
return undefined;
}

const healthInfoJson: any[] = JSON.parse(healthInfo);
const healthInfoList = healthInfoJson.filter(service => service.status !== 'serviceOperational').map((service) => {
return {
Url: `https://admin.microsoft.com/#/servicehealth/:/currentIssues/${encodeURIComponent(service.service)}/`,
Title: service.service
};
});
return healthInfoList;
}

/**
Expand Down
14 changes: 3 additions & 11 deletions src/services/Dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Commands } from './../constants/Commands';
import { Notifications } from './Notifications';
import { execSync } from 'child_process';
import { commands, ProgressLocation, ThemeIcon, window } from 'vscode';
import { commands, ProgressLocation, window } from 'vscode';
import { Logger } from './Logger';
import { NpmLs, Subscription } from '../models';
import { TerminalCommandExecuter } from './TerminalCommandExecuter';
Expand Down Expand Up @@ -94,16 +94,8 @@ export class Dependencies {
/**
* Installs the dependencies by running the npm install command in a terminal.
*/
public static install() {
const terminal = window.createTerminal({
name: 'Installing dependencies',
iconPath: new ThemeIcon('cloud-download')
});

if (terminal) {
terminal.sendText(`npm i -g ${DEPENDENCIES.join(' ')}`);
terminal.show(true);
}
public static async install() {
await TerminalCommandExecuter.runCommand(`npm i -g ${DEPENDENCIES.join(' ')}`, [], 'Installing dependencies', 'cloud-download');
}

/**
Expand Down
21 changes: 5 additions & 16 deletions src/services/TerminalCommandExecuter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { commands, ThemeIcon, workspace, window, Terminal } from 'vscode';
import { Commands, EXTENSION_NAME, NodeVersionManagers } from '../constants';
import { Commands, NodeVersionManagers } from '../constants';
import { Subscription } from '../models';
import { Extension } from './Extension';
import { getPlatform } from '../utils';
import { getPlatform, getExtensionSettings } from '../utils';
import { TeamsToolkitIntegration } from './TeamsToolkitIntegration';
import { Folders } from './Folders';
import { join } from 'path';
Expand Down Expand Up @@ -99,7 +99,7 @@ export class TerminalCommandExecuter {

// Check the user's settings to see if they want to use nvm or nvs
// Get the user's preferred node version manager -- nvm or nvs or none, if they don't want to use either
const nodeVersionManager: string = TerminalCommandExecuter.getExtensionSettings('nodeVersionManager', 'nvm');
const nodeVersionManager: string = getExtensionSettings('nodeVersionManager', 'nvm');

// Check if nvm is used
const nvmFiles = await workspace.findFiles('.nvmrc', '**/node_modules/**');
Expand All @@ -117,17 +117,6 @@ export class TerminalCommandExecuter {
return terminal;
}

/**
* Retrieves the extension settings value for the specified setting.
* If the setting is not found, the default value is returned.
* @param setting - The name of the setting to retrieve.
* @param defaultValue - The default value to return if the setting is not found.
* @returns The value of the setting, or the default value if the setting is not found.
*/
private static getExtensionSettings<T>(setting: string, defaultValue: T): T {
return workspace.getConfiguration(EXTENSION_NAME).get<T>(setting, defaultValue);
}

/**
* Runs a command in the specified terminal.
* @param command - The command to run.
Expand All @@ -145,8 +134,8 @@ export class TerminalCommandExecuter {
* @param command - The command to run.
* @param args - The arguments for the command.
*/
public static async runCommand(command: string, args: string[]) {
const terminal = await TerminalCommandExecuter.createTerminal('Gulp task', 'tasks-list-configure');
public static async runCommand(command: string, args: string[], terminalTitle: string = 'Gulp task', terminalIcon: string = 'tasks-list-configure') {
const terminal = await TerminalCommandExecuter.createTerminal(terminalTitle, terminalIcon);

const wsFolder = await Folders.getWorkspaceFolder();
if (wsFolder) {
Expand Down
Loading