Skip to content

Commit

Permalink
Merge pull request #12232 from OfficeDev/main
Browse files Browse the repository at this point in the history
build: merge back main into dev branch
  • Loading branch information
kimizhu authored Aug 15, 2024
2 parents 2dea697 + b2df3df commit 11679a3
Show file tree
Hide file tree
Showing 40 changed files with 275 additions and 409 deletions.
4 changes: 3 additions & 1 deletion packages/cli/src/userInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ class CLIUserInteraction implements UserInteraction {
const choices = (option as OptionItem[]).map((op) => {
return {
id: op.id,
title: labelClean(op.label),
title: !op.description
? labelClean(op.label)
: labelClean(op.label) + ` (${op.description})`,
detail: op.detail,
};
});
Expand Down
30 changes: 30 additions & 0 deletions packages/cli/tests/unit/ui.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,36 @@ describe("User Interaction Tests", function () {
}
});

it("Add description in title", async () => {
const config: SingleSelectConfig = {
name: "test",
title: "test",
options: [{ id: "id1", description: "some description", label: "label" }],
};
sandbox.stub(UI, "loadSelectDynamicData").resolves(ok({} as any));
sandbox.stub(UI, "singleSelect").resolves(ok("id1"));
const result = await UI.selectOption(config);
expect(result.isOk());
if (result.isOk()) {
expect(result.value.result).equal("id1");
}
});

it("No description in title", async () => {
const config: SingleSelectConfig = {
name: "test",
title: "test",
options: [{ id: "id1", label: "label" }],
};
sandbox.stub(UI, "loadSelectDynamicData").resolves(ok({} as any));
sandbox.stub(UI, "singleSelect").resolves(ok("id1"));
const result = await UI.selectOption(config);
expect(result.isOk());
if (result.isOk()) {
expect(result.value.result).equal("id1");
}
});

it("invalid option", async () => {
sandbox.stub(UI, "singleSelect").resolves(ok("c"));
const config: SingleSelectConfig = {
Expand Down
4 changes: 1 addition & 3 deletions packages/fx-core/src/component/deps-checker/util/cpUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
/* eslint-disable @typescript-eslint/no-namespace */
import * as cp from "child_process";
import * as os from "os";
import * as shellQuote from "shell-quote";

export interface DebugLogger {
debug(message: string): void;
Expand Down Expand Up @@ -57,8 +56,7 @@ export namespace cpUtils {
};
Object.assign(options, additionalOptions);

const quotedCommand = shellQuote.quote([command]);
const childProc: cp.ChildProcess = cp.spawn(quotedCommand, args, options);
const childProc: cp.ChildProcess = cp.spawn(command, args, options);
let timer: NodeJS.Timeout;
if (options.timeout && options.timeout > 0) {
// timeout only exists for exec not spawn
Expand Down
4 changes: 2 additions & 2 deletions packages/fx-core/src/component/generator/apiSpec/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ async function updateAdaptiveCardForCustomApi(
const name = item.item.operationId!.replace(/[^a-zA-Z0-9]/g, "_");
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(item.item, true);
if (jsonPath !== "$" && card.body && card.body[0] && (card.body[0] as any).$data) {
(card.body as any).$data = `\${${jsonPath}}`;
(card.body[0] as any).$data = `\${${jsonPath}}`;
}
const cardFilePath = path.join(adaptiveCardsFolderPath, `${name}.json`);
await fs.writeFile(cardFilePath, JSON.stringify(card, null, 2));
Expand Down Expand Up @@ -1022,7 +1022,7 @@ async def {{operationId}}(
):
parameters = context.data
path = parameters.get("path", {})
body = parameters.get("body", {})
body = parameters.get("body", None)
query = parameters.get("query", {})
resp = client.{{operationId}}(**path, json=body, _headers={}, _params=query, _cookies={})
Expand Down
51 changes: 0 additions & 51 deletions packages/fx-core/src/component/generator/officeAddin/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,55 +259,4 @@ export class OfficeAddinGeneratorNew extends DefaultTemplateGenerator {
if (res.isErr()) return err(res.error);
return Promise.resolve(ok([{ templateName: tplName, language: lang }]));
}

public async post(
context: Context,
inputs: Inputs,
destinationPath: string,
actionContext?: ActionContext
): Promise<Result<GeneratorResult, FxError>> {
const res = await OfficeAddinGenerator.doScaffolding(context, inputs, destinationPath);
if (res.isErr()) return err(res.error);
await this.fixIconPath(destinationPath);
return ok({});
}

/**
* this is a work around for MOS API bug that will return invalid package if the icon path is not root folder of appPackage
* so this function will move the two icon files to root folder of appPackage and update the manifest.json
*/
async fixIconPath(projectPath: string): Promise<void> {
const outlineOldPath = join(projectPath, "appPackage", "assets", "outline.png");
const colorOldPath = join(projectPath, "appPackage", "assets", "color.png");
const outlineNewPath = join(projectPath, "appPackage", "outline.png");
const colorNewPath = join(projectPath, "appPackage", "color.png");
const manifestPath = join(projectPath, "appPackage", "manifest.json");
if (!(await fse.pathExists(manifestPath))) return;
const manifest = await fse.readJson(manifestPath);
let change = false;
if (manifest.icons.outline === "assets/outline.png") {
if ((await fse.pathExists(outlineOldPath)) && !(await fse.pathExists(outlineNewPath))) {
await fse.move(outlineOldPath, outlineNewPath);
manifest.icons.outline = "outline.png";
change = true;
}
}
if (manifest.icons.color === "assets/color.png") {
if ((await fse.pathExists(colorOldPath)) && !(await fse.pathExists(colorNewPath))) {
await fse.move(colorOldPath, colorNewPath);
manifest.icons.color = "color.png";
change = true;
}
}
if (change) {
await fse.writeJson(manifestPath, manifest, { spaces: 4 });
const webpackConfigPath = join(projectPath, "webpack.config.js");
const content = await fse.readFile(webpackConfigPath, "utf8");
const newContent = content.replace(
'from: "appPackage/assets/*",\r\n to: "assets/[name][ext][query]",\r\n },',
'from: "appPackage/assets/*",\r\n to: "assets/[name][ext][query]",\r\n },\r\n {\r\n from: "appPackage/*.png",\r\n to: "[name]" + "[ext]",\r\n },'
);
await fse.writeFile(webpackConfigPath, newContent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1033,191 +1033,6 @@ describe("OfficeAddinGeneratorNew", () => {
chai.assert.isTrue(res.isErr());
});
});

describe("post()", () => {
const sandbox = sinon.createSandbox();
afterEach(() => {
sandbox.restore();
});
it(`happy`, async () => {
const inputs: Inputs = {
platform: Platform.CLI,
projectPath: "./",
};
sandbox.stub(OfficeAddinGenerator, "doScaffolding").resolves(ok(undefined));
sandbox.stub(generator, "fixIconPath").resolves();
const res = await generator.post(context, inputs, "./");
chai.assert.isTrue(res.isOk());
});
});
});

describe("fixIconPath()", () => {
const generator = new OfficeAddinGeneratorNew();
const sandbox = sinon.createSandbox();
beforeEach(() => {
sandbox.stub(fse, "readFile").resolves("" as any);
sandbox.stub(fse, "writeFile").resolves();
});
afterEach(() => {
sandbox.restore();
});
it("manifest not found", async () => {
sandbox.stub(fse, "pathExists").resolves(false);
const move = sandbox.stub(fse, "move").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.notCalled);
});
it("happy", async () => {
sandbox.stub(fse, "pathExists").callsFake(async (path) => {
if (path.endsWith("manifest.json")) {
return true;
} else if (path.endsWith("assets/outline.png") || path.endsWith("assets\\outline.png")) {
return true;
} else if (path.endsWith("assets/color.png") || path.endsWith("assets\\color.png")) {
return true;
} else if (path.endsWith("color.png")) {
return false;
} else if (path.endsWith("outline.png")) {
return false;
}
});
sandbox
.stub(fse, "readJson")
.resolves({ icons: { outline: "assets/outline.png", color: "assets/color.png" } });
const move = sandbox.stub(fse, "move").resolves();
const writeJson = sandbox.stub(fse, "writeJson").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.calledTwice);
chai.assert.isTrue(writeJson.calledOnce);
});
it("no need to move", async () => {
sandbox.stub(fse, "pathExists").callsFake(async (path) => {
if (path.endsWith("manifest.json")) {
return true;
} else if (path.endsWith("assets/outline.png") || path.endsWith("assets\\outline.png")) {
return true;
} else if (path.endsWith("assets/color.png") || path.endsWith("assets\\color.png")) {
return true;
} else if (path.endsWith("color.png")) {
return false;
} else if (path.endsWith("outline.png")) {
return false;
}
});
sandbox
.stub(fse, "readJson")
.resolves({ icons: { outline: "outline.png", color: "color.png" } });
const move = sandbox.stub(fse, "move").resolves();
const writeJson = sandbox.stub(fse, "writeJson").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.notCalled);
chai.assert.isTrue(writeJson.notCalled);
});
it("no need to move", async () => {
sandbox.stub(fse, "pathExists").callsFake(async (path) => {
if (path.endsWith("manifest.json")) {
return true;
} else if (path.endsWith("assets/outline.png") || path.endsWith("assets\\outline.png")) {
return false;
} else if (path.endsWith("assets/color.png") || path.endsWith("assets\\color.png")) {
return false;
} else if (path.endsWith("color.png")) {
return false;
} else if (path.endsWith("outline.png")) {
return false;
}
});
sandbox
.stub(fse, "readJson")
.resolves({ icons: { outline: "assets/outline.png", color: "assets/color.png" } });
const move = sandbox.stub(fse, "move").resolves();
const writeJson = sandbox.stub(fse, "writeJson").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.notCalled);
chai.assert.isTrue(writeJson.notCalled);
});
describe("fixIconPath()", () => {
const sandbox = sinon.createSandbox();
afterEach(() => {
sandbox.restore();
});
it("manifest not found", async () => {
sandbox.stub(fse, "pathExists").resolves(false);
const move = sandbox.stub(fse, "move").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.notCalled);
});
it("happy", async () => {
sandbox.stub(fse, "pathExists").callsFake(async (path) => {
if (path.endsWith("manifest.json")) {
return true;
} else if (path.endsWith("assets/outline.png") || path.endsWith("assets\\outline.png")) {
return true;
} else if (path.endsWith("assets/color.png") || path.endsWith("assets\\color.png")) {
return true;
} else if (path.endsWith("color.png")) {
return false;
} else if (path.endsWith("outline.png")) {
return false;
}
});
sandbox
.stub(fse, "readJson")
.resolves({ icons: { outline: "assets/outline.png", color: "assets/color.png" } });
const move = sandbox.stub(fse, "move").resolves();
const writeJson = sandbox.stub(fse, "writeJson").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.calledTwice);
chai.assert.isTrue(writeJson.calledOnce);
});
it("no need to move", async () => {
sandbox.stub(fse, "pathExists").callsFake(async (path) => {
if (path.endsWith("manifest.json")) {
return true;
} else if (path.endsWith("assets/outline.png") || path.endsWith("assets\\outline.png")) {
return true;
} else if (path.endsWith("assets/color.png") || path.endsWith("assets\\color.png")) {
return true;
} else if (path.endsWith("color.png")) {
return false;
} else if (path.endsWith("outline.png")) {
return false;
}
});
sandbox
.stub(fse, "readJson")
.resolves({ icons: { outline: "outline.png", color: "color.png" } });
const move = sandbox.stub(fse, "move").resolves();
const writeJson = sandbox.stub(fse, "writeJson").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.notCalled);
chai.assert.isTrue(writeJson.notCalled);
});
it("no need to move", async () => {
sandbox.stub(fse, "pathExists").callsFake(async (path) => {
if (path.endsWith("manifest.json")) {
return true;
} else if (path.endsWith("assets/outline.png") || path.endsWith("assets\\outline.png")) {
return false;
} else if (path.endsWith("assets/color.png") || path.endsWith("assets\\color.png")) {
return false;
} else if (path.endsWith("color.png")) {
return false;
} else if (path.endsWith("outline.png")) {
return false;
}
});
sandbox
.stub(fse, "readJson")
.resolves({ icons: { outline: "assets/outline.png", color: "assets/color.png" } });
const move = sandbox.stub(fse, "move").resolves();
const writeJson = sandbox.stub(fse, "writeJson").resolves();
await generator.fixIconPath("./");
chai.assert.isTrue(move.notCalled);
chai.assert.isTrue(writeJson.notCalled);
});
});
});

describe("doScaffolding()", () => {
Expand Down
33 changes: 33 additions & 0 deletions packages/vscode-extension/PRERELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@

> Note: This changelog only includes the changes for the pre-release versions of Teams Toolkit. For the changelog of stable versions, please refer to the [Teams Toolkit Changelog](https://github.com/OfficeDev/TeamsFx/blob/dev/packages/vscode-extension/CHANGELOG.md).
### August 14, 2024

#### New Features
- **Enhanced App Validation**: Developers can now evaluate their app packages using the same test cases Microsoft employs during app review. The Enhanced App Validation feature in Teams Toolkit identifies any errors or warnings within your app package and provides clear guidelines for resolution. For more details on Microsoft test cases, refer to the [Teams Store validation guidelines](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/appsource/prepare/teams-store-validation-guidelines) and [Commercial marketplace certification policies](https://learn.microsoft.com/en-us/legal/marketplace/certification-policies).
![App Validation](https://github.com/user-attachments/assets/4c2b8c49-6a0a-4ea7-8796-a94464714463)

- **Generate an Intelligent Chatbot with Python**: Following the release of support for building [Custom Engine Copilot](https://learn.microsoft.com/microsoft-365-copilot/extensibility/overview-custom-engine-copilot) during Build 2024, which included the ability to "chat with" your own API, Teams Toolkit now extends this capability to the Python programming language.
![App Generator](https://github.com/user-attachments/assets/21efa344-aea5-4d44-bb78-aa8e26dc68a1)

- **Create Declarative Copilot**: Teams Toolkit now allows you to build a declarative copilot, enabling you to customize Microsoft 365 Copilot by declaring specific instructions, actions, and knowledge. Declarative copilots run on the same orchestrator, foundation models, and trusted AI services that power Microsoft Copilot. You can learn more about [declarative copilots here](https://learn.microsoft.com/microsoft-365-copilot/extensibility/overview-declarative-copilot). The toolkit supports the creation of both basic declarative copilots and those with an API plugin.
![Declarative Copilot](https://github.com/user-attachments/assets/37412cdd-c7e8-4e38-bd45-794997b050ec)

- **Using Assistant API on Azure OpenAI Service**: The Teams Toolkit has updated the `AI Agent` (Python) app template to support the Assistant API on Azure OpenAI Service. You can now build your own AI Agents on Microsoft 365 using Python, with the option to use either Azure OpenAI Service or OpenAI directly. Support for TypeScript and JavaScript is forthcoming.

#### Enhancements

- Teams Toolkit will continue to update scaffold app templates to ensure compliance with [Teams Store validation guidelines](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/appsource/prepare/teams-store-validation-guidelines). The first round of updates focuses on bot templates, including:
- [PR#12063](https://github.com/OfficeDev/teams-toolkit/pull/12063): Updated `Basic Bot` and `Message Extension`
- [PR#12096](https://github.com/OfficeDev/teams-toolkit/pull/12096): Updated `Chat Command`
- [PR#12123](https://github.com/OfficeDev/teams-toolkit/pull/12123): Updated `Chat Notification Messages`
- [PR#12119](https://github.com/OfficeDev/teams-toolkit/pull/12119): Updated `Sequential Workflow in Chat`
- Teams Toolkit now prompts users to generate an API key before debugging API ME or API Plugin with API Key authentication templates.
- Secret values have been redacted from the Visual Studio Code output channel.

#### Bug Fixes

- Fixed vulnerability issues in TeamsFx SDK. [#11973](https://github.com/OfficeDev/teams-toolkit/pull/11937)
- Resolved compatibility issues with `groupchat` and `groupChat` in the Teams app manifest. [#12028](https://github.com/OfficeDev/teams-toolkit/pull/12028)
- Corrected an issue where the link redirection for the lifecycle `Provision` button was incorrect. [#12120](https://github.com/OfficeDev/teams-toolkit/pull/12120)
- Fixed initialization failures of `publicClientApplication` in TeamsFx SDK. [#12159](https://github.com/OfficeDev/teams-toolkit/pull/12159)
- Addressed issues when creating SharePoint Framework-based tab apps. [#12173](https://github.com/OfficeDev/teams-toolkit/pull/12173)


### July 17, 2024

#### New Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "./offlinePage.scss";

import * as React from "react";

import OfflineImage from "../../../img/webview/sample/offline.svg";
import OfflineImage from "../../../img/webview/sample/offline.svg?react";

export default class OfflinePage extends React.Component<unknown, unknown> {
constructor(props: unknown) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as React from "react";

import { Image } from "@fluentui/react";

import Turtle from "../../../img/webview/sample/turtle.svg";
import Turtle from "../../../img/webview/sample/turtle.svg?react";
import { TelemetryTriggerFrom } from "../../telemetry/extTelemetryEvents";
import { SampleProps } from "./ISamples";

Expand Down
Loading

0 comments on commit 11679a3

Please sign in to comment.