Skip to content

Commit

Permalink
Merge branch 'hui/api-key-improvement' of github.com:OfficeDev/TeamsF…
Browse files Browse the repository at this point in the history
…x into hui/api-key-improvement
  • Loading branch information
huimiu committed Jul 15, 2024
2 parents 46b80fe + cce3e1a commit 6ccef43
Show file tree
Hide file tree
Showing 77 changed files with 2,027 additions and 147 deletions.
19 changes: 19 additions & 0 deletions packages/cli/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@
"${workspaceFolder}/../api/build/**/*.js"
],
"console": "integratedTerminal"
},
{
"type": "pwa-node",
"request": "launch",
"name": "Launch uninstall command",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/cli.js",
"args": ["uninstall"],
"outFiles": [
"${workspaceFolder}/lib/**/*.js",
"${workspaceFolder}/../fx-core/build/**/*.js",
"${workspaceFolder}/../api/build/**/*.js"
],
"resolveSourceMapLocations": [
"${workspaceFolder}/lib/**/*.js",
"${workspaceFolder}/../fx-core/build/**/*.js",
"${workspaceFolder}/../api/build/**/*.js"
],
"console": "integratedTerminal"
}
]
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ class CLIEngine {
context.optionValues.platform = Platform.CLI;
// set projectPath
const projectFolderOption = context.command.options?.find(
(o) => o.questionName === "projectPath"
(o) => o.questionName === "projectPath" && o.required
);
if (projectFolderOption) {
// resolve projectPath
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/models/addPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { TelemetryEvent } from "../../telemetry/cliTelemetryEvents";
import { ProjectFolderOption } from "../common";

export const addPluginCommand: CLICommand = {
name: "copilot-plugin",
description: commands["add.copilot-plugin"].description,
name: "plugin",
description: commands["add.plugin"].description,
options: [...AddPluginOptions, ProjectFolderOption],
telemetry: {
event: TelemetryEvent.AddCopilotPlugin,
Expand Down
64 changes: 44 additions & 20 deletions packages/cli/src/commands/models/m365Unacquire.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { CLICommand, err, ok } from "@microsoft/teamsfx-api";
import { PackageService } from "@microsoft/teamsfx-core";
import { UninstallInputs, QuestionNames } from "@microsoft/teamsfx-core";
import { logger } from "../../commonlib/logger";
import { MissingRequiredOptionError } from "../../error";
import { commands } from "../../resource";
import { TelemetryEvent } from "../../telemetry/cliTelemetryEvents";
import { m365utils, sideloadingServiceEndpoint } from "./m365Sideloading";
import { getFxCore } from "../../activate";

export const m365UnacquireCommand: CLICommand = {
name: "uninstall",
aliases: ["unacquire"],
description: commands.uninstall.description,
options: [
{
name: "title-id",
name: QuestionNames.UninstallMode,
description: commands.uninstall.options["mode"],
type: "string",
},
{
name: QuestionNames.TitleId,
description: commands.uninstall.options["title-id"],
type: "string",
},
{
name: "manifest-id",
name: QuestionNames.ManifestId,
description: commands.uninstall.options["manifest-id"],
type: "string",
},
{
name: QuestionNames.Env,
description: commands.uninstall.options["env"],
type: "string",
},
{
name: "folder",
questionName: QuestionNames.ProjectPath,
description: commands.uninstall.options["folder"],
type: "string",
},
{
name: QuestionNames.UninstallOptions,
description: commands.uninstall.options["options"],
type: "array",
},
],
examples: [
{
command: `${process.env.TEAMSFX_CLI_BIN_NAME} uninstall --title-id U_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`,
description: "Remove the acquired M365 App by Title ID",
command: `${process.env.TEAMSFX_CLI_BIN_NAME} uninstall -i false --mode title-id --title-id U_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`,
description: "Remove the acquired Microsoft 365 Application using Title ID",
},
{
command: `${process.env.TEAMSFX_CLI_BIN_NAME} uninstall -i false --mode manifest-id --manifest-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --options 'm365-app,app-registration,bot-framework-registration'`,
description: "Remove the acquired Microsoft 365 Application using Manifest ID",
},
{
command: `${process.env.TEAMSFX_CLI_BIN_NAME} uninstall -i false --mode env --env xxx --options 'm365-app,app-registration,bot-framework-registration' --folder ./myapp`,
description:
"Remove the acquired Microsoft 365 Application using environment in Teams Toolkit generated project",
},
{
command: `${process.env.TEAMSFX_CLI_BIN_NAME} uninstall --manifest-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`,
description: "Remove the acquired M365 App by Manifest ID",
command: `${process.env.TEAMSFX_CLI_BIN_NAME} uninstall`,
description: "Uninstall in interactive mode",
},
],
telemetry: {
event: TelemetryEvent.M365Unacquire,
},
defaultInteractiveOption: false,
defaultInteractiveOption: true,
handler: async (ctx) => {
const packageService = new PackageService(sideloadingServiceEndpoint, logger);
let titleId = ctx.optionValues["title-id"] as string;
const manifestId = ctx.optionValues["manifest-id"] as string;
if (titleId === undefined && manifestId === undefined) {
return err(
new MissingRequiredOptionError(ctx.command.fullName, `--title-id or --manifest-id`)
);
}
const tokenAndUpn = await m365utils.getTokenAndUpn();
if (titleId === undefined) {
titleId = await packageService.retrieveTitleId(tokenAndUpn[0], manifestId);
const inputs = ctx.optionValues as UninstallInputs;
const core = getFxCore();
const res = await core.uninstall(inputs);
if (res.isErr()) {
return err(res.error);
}
await packageService.unacquire(tokenAndUpn[0], titleId);
return ok(undefined);
},
};
12 changes: 8 additions & 4 deletions packages/cli/src/resource/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"add.spfx-web-part": {
"description": "Auto-hosted SPFx web part tightly integrated with Microsoft Teams."
},
"add.copilot-plugin": {
"add.plugin": {
"description": "A plugin to extend Copilot using your APIs."
},
"create": {
Expand Down Expand Up @@ -144,10 +144,14 @@
"description": "Run the publish stage in teamsapp.yml."
},
"uninstall": {
"description": "Remove an acquired M365 App.",
"description": "Clean up resources associated with Manifest ID, Title ID, or an environment in Teams Toolkit generated project. Resources include app registration in Teams Developer Portal, bot registration in Bot Framework Portal, and uploaded custom apps in Microsoft 365 apps.",
"options": {
"title-id": "Title ID of the acquired M365 App.",
"manifest-id": "Manifest ID of the acquired M365 App."
"mode": "Choose a way to clean up resources.",
"title-id": "Title ID of the App.",
"manifest-id": "Manifest ID of the App.",
"env": "The specific environment in the project created by Teams Toolkit.",
"folder": "Project path to uninstall, only used in env mode.",
"options": "Selected resourecs to uninstall. example: --options m365-app,app-registration,bot-framework-registration"
}
},
"update": {
Expand Down
35 changes: 10 additions & 25 deletions packages/cli/tests/unit/commands.tests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CLIContext, err, ok } from "@microsoft/teamsfx-api";
import { CLIContext, SystemError, err, ok } from "@microsoft/teamsfx-api";
import {
CollaborationStateResult,
FeatureFlags,
Expand Down Expand Up @@ -237,7 +237,7 @@ describe("CLI commands", () => {
it("success", async () => {
sandbox.stub(FxCore.prototype, "addPlugin").resolves(ok(undefined));
const ctx: CLIContext = {
command: { ...addPluginCommand, fullName: "add copilot-plugin" },
command: { ...addPluginCommand, fullName: "add plugin" },
optionValues: {},
globalOptionValues: {},
argumentValues: [],
Expand Down Expand Up @@ -775,8 +775,8 @@ describe("CLI commands", () => {
beforeEach(() => {
sandbox.stub(logger, "warning");
});
it("MissingRequiredOptionError", async () => {
sandbox.stub(m365utils, "getTokenAndUpn").resolves(["token", "upn"]);
it("success", async () => {
sandbox.stub(FxCore.prototype, "uninstall").resolves(ok(undefined));
const ctx: CLIContext = {
command: { ...m365UnacquireCommand, fullName: "teamsfx" },
optionValues: {},
Expand All @@ -785,34 +785,19 @@ describe("CLI commands", () => {
telemetryProperties: {},
};
const res = await m365UnacquireCommand.handler!(ctx);
assert.isTrue(res.isErr());
});
it("success retrieveTitleId", async () => {
sandbox.stub(m365utils, "getTokenAndUpn").resolves(["token", "upn"]);
sandbox.stub(PackageService.prototype, "retrieveTitleId").resolves("id");
sandbox.stub(PackageService.prototype, "unacquire").resolves();
const ctx: CLIContext = {
command: { ...m365UnacquireCommand, fullName: "teamsfx" },
optionValues: { "manifest-id": "aaa" },
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const res = await m365UnacquireCommand.handler!(ctx);
assert.isTrue(res.isOk());
});
it("success", async () => {
sandbox.stub(m365utils, "getTokenAndUpn").resolves(["token", "upn"]);
sandbox.stub(PackageService.prototype, "unacquire").resolves();
it("failed", async () => {
sandbox.stub(FxCore.prototype, "uninstall").resolves(err(new SystemError("", "", "")));
const ctx: CLIContext = {
command: { ...m365UnacquireCommand, fullName: "teamsfx" },
optionValues: { "title-id": "aaa" },
optionValues: {},
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const res = await m365UnacquireCommand.handler!(ctx);
assert.isTrue(res.isOk());
assert.isTrue(res.isErr());
});
});

Expand Down Expand Up @@ -1148,7 +1133,7 @@ describe("CLI read-only commands", () => {
};
const res = await listTemplatesCommand.handler!(ctx);
assert.isTrue(res.isOk());
assert.isFalse(!!messages.find((msg) => msg.includes("copilot-plugin-existing-api")));
assert.isFalse(!!messages.find((msg) => msg.includes("api-plugin-existing-api")));
});
it("table with description", async () => {
const ctx: CLIContext = {
Expand Down Expand Up @@ -1186,7 +1171,7 @@ describe("CLI read-only commands", () => {
};
const res = await listTemplatesCommand.handler!(ctx);
assert.isTrue(res.isOk());
assert.isTrue(!!messages.find((msg) => msg.includes("copilot-plugin-existing-api")));
assert.isTrue(!!messages.find((msg) => msg.includes("api-plugin-existing-api")));
});
});
describe("listSamplesCommand", async () => {
Expand Down
27 changes: 27 additions & 0 deletions packages/fx-core/resource/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,33 @@
"core.copilot.addAPI.success": "%s have(has) been successfully added to %s",
"core.copilot.addAPI.InjectAPIKeyActionFailed": "Inject API key action to teamsapp.yaml file unsuccessful, make sure the file contains teamsApp/create action in provision section.",
"core.copilot.addAPI.InjectOAuthActionFailed": "Inject OAuth action to teamsapp.yaml file unsuccessful, make sure the file contains teamsApp/create action in provision section.",
"core.uninstall.botNotFound": "Cannot find bot using the manifest ID %s",
"core.uninstall.confirm.tdp": "App registration of manifest ID: %s will be removed. Please confirm.",
"core.uninstall.confirm.m365App": "Microsoft 365 Application of Title ID: %s will be uninstalled. Please confirm.",
"core.uninstall.confirm.bot": "Bot framework registration of bot ID: %s will be removed. Please confirm.",
"core.uninstall.confirm.cancel.tdp": "Removal of app registration is canceled.",
"core.uninstall.confirm.cancel.m365App": "Uninstallation of Microsoft 365 Application is canceled.",
"core.uninstall.confirm.cancel.bot": "Removal of Bot framework registration is canceled.",
"core.uninstall.success.tdp": "App registration of manifest ID: %s successfully removed.",
"core.uninstall.success.m365App": "Microsoft 365 Application of Title ID: %s successfully uninstalled.",
"core.uninstall.success.delayWarning": "The uninstallation of the Microsoft 365 Application may be delayed.",
"core.uninstall.success.bot": "Bot framework registration of bot ID: %s successfully removed.",
"core.uninstall.failed.titleId": "Unable to find the Title ID. This app is probably not installed.",
"core.uninstallQuestion.manifestId": "Manifest ID",
"core.uninstallQuestion.env": "Environment",
"core.uninstallQuestion.titleId": "Title ID",
"core.uninstallQuestion.chooseMode": "Choose a way to clean up resources",
"core.uninstallQuestion.manifestIdMode": "Manifest ID",
"core.uninstallQuestion.manifestIdMode.detail": "Clean up resources associated with Manifest ID. This includes app registration in Teams Developer Portal, bot registration in Bot Framework Portal, and custom apps uploaded to Microsoft 365. You can find the Manifest ID in the environment file (default environment key: Teams_App_ID) in the project created by Teams Toolkit.",
"core.uninstallQuestion.envMode": "Environment in Teams Toolkit Created Project",
"core.uninstallQuestion.envMode.detail": "Clean up resources associated with a specific environment in the Teams Toolkit created project. Resources include app registration in Teams Developer Portal, bot registration in Bot Framework Portal, and custom apps uploaded in Microsoft 365 apps.",
"core.uninstallQuestion.titleIdMode": "Title ID",
"core.uninstallQuestion.titleIdMode.detail": "Uninstall the uploaded custom app associated with Title ID. The Title ID can be found in the environment file in the Teams Toolkit created project.",
"core.uninstallQuestion.chooseOption": "Choose resources to uninstall",
"core.uninstallQuestion.m365Option": "Microsoft 365 Application",
"core.uninstallQuestion.tdpOption": "App registration",
"core.uninstallQuestion.botOption": "Bot framework registration",
"core.uninstallQuestion.projectPath": "Project path",
"ui.select.LoadingOptionsPlaceholder": "Loading options ...",
"ui.select.LoadingDefaultPlaceholder": "Loading default value ...",
"error.aad.manifest.NameIsMissing": "name is missing\n",
Expand Down
10 changes: 9 additions & 1 deletion packages/fx-core/src/client/teamsDevPortalClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,15 @@ export class TeamsDevPortalClient {
}
throw new Error(`Cannot get the app definition with app ID ${teamsAppId}`);
}

@hooks([ErrorContextMW({ source: "Teams", component: "TeamsDevPortalClient" })])
async getBotId(token: string, teamsAppId: string): Promise<string | undefined> {
const app = await this.getApp(token, teamsAppId);
if (app?.bots?.length && app.bots.length > 0) {
return app.bots[0].botId;
}
TOOLS.logProvider?.error(`botId not found. Input: ${teamsAppId}`);
return undefined;
}
@hooks([ErrorContextMW({ source: "Teams", component: "TeamsDevPortalClient" })])
async getAppPackage(token: string, teamsAppId: string): Promise<any> {
TOOLS.logProvider?.info("Downloading app package for app " + teamsAppId);
Expand Down
8 changes: 5 additions & 3 deletions packages/fx-core/src/component/generator/apiSpec/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
CustomCopilotRagOptions,
ProgrammingLanguage,
QuestionNames,
copilotPluginApiSpecOptionId,
apiPluginApiSpecOptionId,
} from "../../../question/constants";
import { SummaryConstant } from "../../configManager/constant";
import { manifestUtils } from "../../driver/teamsApp/utils/ManifestUtils";
Expand Down Expand Up @@ -124,7 +124,9 @@ export async function listOperations(
shouldLogWarning = true,
existingCorrelationId?: string
): Promise<Result<ApiOperation[], ErrorResult[]>> {
const isPlugin = inputs[QuestionNames.Capabilities] === copilotPluginApiSpecOptionId;
const isPlugin =
inputs[QuestionNames.Capabilities] === apiPluginApiSpecOptionId ||
!!inputs[QuestionNames.PluginAvailability];
const isCustomApi =
inputs[QuestionNames.CustomCopilotRag] === CustomCopilotRagOptions.customApi().id;

Expand Down Expand Up @@ -683,7 +685,7 @@ function mapInvalidReasonToMessage(reason: ErrorType): string {
}

function formatValidationErrorContent(error: ApiSpecErrorResult, inputs: Inputs): string {
const isPlugin = inputs[QuestionNames.Capabilities] === copilotPluginApiSpecOptionId;
const isPlugin = inputs[QuestionNames.Capabilities] === apiPluginApiSpecOptionId;
try {
switch (error.type) {
case ErrorType.SpecNotValid: {
Expand Down
Loading

0 comments on commit 6ccef43

Please sign in to comment.