Skip to content

feat: add createSPEContainerType custom action. Add unit tests #14239

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

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions packages/fx-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"test:metadataUtil": "nyc mocha \"tests/component/util/metadataUtil.test.ts\"",
"test:migrate": "nyc mocha \"tests/component/migrate.test.ts\"",
"test:retry": "npx mocha \"tests/core/middleware/retry.test.ts\"",
"test:sharePointEmbeddedContainerType": "nyc mocha \"tests/component/driver/sharePointEmbeddedContainerType/*.test.ts\"",
"clean": "rm -rf build",
"prebuild": "npm run gen:cli",
"build": "tsc -p ./ --incremental",
Expand Down
8 changes: 8 additions & 0 deletions packages/fx-core/resource/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,14 @@
"driver.devChannel.install.description": "Install app into sandboxed channel.",
"driver.devChannel.install.progress.message": "Installing app...",
"driver.devChannel.install.summary.exists": "App '%s' already installed in team '%s'. It will now be updated to the latest version.",
"driver.sharePointEmbeddedContainerType.description.createContainerType": "Create a SharePoint Embedded Container type",
"driver.sharePointEmbeddedContainerType.progressBar.createContainerTypeTitle": "Creating a SharePoint Embedded container type...",
"driver.sharePointEmbeddedContainerType.log.startExecuteDriver": "Executing action %s",
"driver.sharePointEmbeddedContainerType.log.successExecuteDriver": "Action %s executed successfully",
"driver.sharePointEmbeddedContainerType.log.failExecuteDriver": "Unable to execute action %s. Error message: %s",
"driver.sharePointEmbeddedContainerType.log.startCreateContainerType": "Environment variable %s does not exist, creating a new SharePointEmbedded container type...",
"driver.sharePointEmbeddedContainerType.log.successCreateContainerType": "Created SharePoint Embedded container type with container type id %s",
"driver.sharePointEmbeddedContainerType.log.skipCreateCreateContainerType": "Environment variable %s already exist, skipping new SharePoint Embedded container type creation step.",
"error.installApp.outsideSandbox": "Unable to install app outside sandboxed Team. Please update TEAM_ID and CHANNEL_ID.",
"error.yaml.InvalidYamlSchemaError": "Unable to parse yaml file: %s. Please open the yaml file for detailed errors.",
"error.yaml.InvalidYamlSchemaErrorWithReason": "Unable to parse yaml file: %s. Reason: %s Please review the yaml file or upgrade to the latest Microsoft 365 Agents Toolkit.",
Expand Down
62 changes: 61 additions & 1 deletion packages/fx-core/resource/yaml-schema/v1.9/yaml.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
{ "$ref": "#/definitions/shareToOthers"},
{ "$ref": "#/definitions/devChannelCreate"},
{ "$ref": "#/definitions/devChannelInstallApp"},
{ "$ref": "#/definitions/typeSpecCompile"}
{ "$ref": "#/definitions/typeSpecCompile"},
{ "$ref": "#/definitions/sharePointEmbeddedContainerTypeCreate"}
]
}
},
Expand Down Expand Up @@ -2102,6 +2103,65 @@
}
}
}
},
"sharePointEmbeddedContainerTypeCreate": {
"type": "object",
"additionalProperties": false,
"description": "Create a new SharePointEmbedded container type.",
"required": ["uses", "with", "writeToEnvironmentFile"],
"properties": {
"name": {
"type": "string",
"description": "An optional name of this action."
},
"env": {
"type": "object",
"description": "Define environment variables for this action.",
"additionalProperties": {
"type": "string"
}
},
"uses": {
"type": "string",
"description": "This action will create a new SPE container type. TODO add more description",
"const": "sharePointEmbeddedContainerType/create"
},
"with": {
"type": "object",
"additionalProperties": false,
"description": "Parameters for this action",
"required": ["owningApplicationId", "billingClassification", "name", "discoverable"],
"properties": {
"owningApplicationId": {
"type": "string",
"description": "application Id for the owning application for the SPE container type"
},
"billingClassification": {
"type": "string",
"description": "The billingClassification for the container type. Can be standard, trial, dtc"
},
"name": {
"type": "string",
"description": "The name for the container type"
},
"discoverable": {
"type": "boolean",
"description": "determines whether the drive items in the container of the container type are discoverable"
}
}
},
"writeToEnvironmentFile": {
"type": "object",
"additionalProperties": false,
"description": "Write environment variables to environment file",
"required": ["containerTypeId"],
"properties": {
"containerTypeId": {
"$ref": "#/definitions/envVarName"
}
}
}
}
}
}
}
62 changes: 61 additions & 1 deletion packages/fx-core/resource/yaml-schema/yaml.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
{ "$ref": "#/definitions/shareToOthers"},
{ "$ref": "#/definitions/devChannelCreate"},
{ "$ref": "#/definitions/devChannelInstallApp"},
{ "$ref": "#/definitions/typeSpecCompile"}
{ "$ref": "#/definitions/typeSpecCompile"},
{ "$ref": "#/definitions/sharePointEmbeddedContainerTypeCreate"}
]
}
},
Expand Down Expand Up @@ -2102,6 +2103,65 @@
}
}
}
},
"sharePointEmbeddedContainerTypeCreate": {
"type": "object",
"additionalProperties": false,
"description": "Create a new SharePointEmbedded container type.",
"required": ["uses", "with", "writeToEnvironmentFile"],
"properties": {
"name": {
"type": "string",
"description": "An optional name of this action."
},
"env": {
"type": "object",
"description": "Define environment variables for this action.",
"additionalProperties": {
"type": "string"
}
},
"uses": {
"type": "string",
"description": "This action will create a new SPE container type. TODO add more description",
"const": "sharePointEmbeddedContainerType/create"
},
"with": {
"type": "object",
"additionalProperties": false,
"description": "Parameters for this action",
"required": ["owningApplicationId", "billingClassification", "name", "discoverable"],
"properties": {
"owningApplicationId": {
"type": "string",
"description": "application Id for the owning application for the SPE container type"
},
"billingClassification": {
"type": "string",
"description": "The billingClassification for the container type. Can be standard, trial, dtc"
},
"name": {
"type": "string",
"description": "The name for the container type"
},
"discoverable": {
"type": "boolean",
"description": "determines whether the drive items in the container of the container type are discoverable"
}
}
},
"writeToEnvironmentFile": {
"type": "object",
"additionalProperties": false,
"description": "Write environment variables to environment file",
"required": ["containerTypeId"],
"properties": {
"containerTypeId": {
"$ref": "#/definitions/envVarName"
}
}
}
}
}
}
}
1 change: 1 addition & 0 deletions packages/fx-core/src/component/driver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ import "./oauth/create";
import "./oauth/update";
import "./share/shareToOthers";
import "./typeSpec/compile";
import "./sharePointEmbeddedContainerType/create";
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { hooks } from "@feathersjs/hooks/lib";
import { FxError, SystemError, UserError, err, ok, Result } from "@microsoft/teamsfx-api";
import axios from "axios";
import { Service } from "typedi";
import { GraphScopes } from "../../../common/constants";
import { getLocalizedString } from "../../../common/localizeUtils";
import { CreateSPEContainerTypeArgs } from "./interface/createSPEContainerTypeArgs";
import { CreateSPEContainerTypeOutput, OutputKeys } from "./interface/createSPEContainerTypeOutput";
import { SPContainerTypeBillingClassification } from "./interface/sharePointEmbeddedContainerType";
import { DriverContext } from "../interface/commonArgs";
import { ExecutionResult, StepDriver } from "../interface/stepDriver";
import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry";
import { loadStateFromEnv, mapStateToEnv } from "../util/utils";
import { logMessageKeys, descriptionMessageKeys, progressBarKeys } from "./utility/constants";
import { WrapDriverContext } from "../util/wrapUtil";
import { SPEContainerTypeAppClient } from "./utility/speContainerTypeAppClient";
import { OutputEnvironmentVariableUndefinedError } from "../error/outputEnvironmentVariableUndefinedError";
import { HttpServerError, InvalidActionInputError, assembleError } from "../../../error/common";

const actionName = "sharePointEmbeddedContainerType/create"; // DO NOT MODIFY THE NAME

@Service(actionName) // DO NOT MODIFY THE SERVICE NAME
export class CreateSharePointEmbeddedContainerTypeDriver implements StepDriver {
// TODO localize these strings
description = getLocalizedString(descriptionMessageKeys.createContainerType);
readonly progressTitle = getLocalizedString(progressBarKeys.progressBarTitle);

public async execute(
args: CreateSPEContainerTypeArgs,
context: DriverContext,
outputEnvVarNames?: Map<string, string>
): Promise<ExecutionResult> {
const wrapDriverContext = new WrapDriverContext(context, actionName, actionName);
return await this.executeInternal(args, wrapDriverContext, outputEnvVarNames);
}

@hooks([addStartAndEndTelemetry(actionName, actionName)])
private async executeInternal(
args: CreateSPEContainerTypeArgs,
context: WrapDriverContext,
outputEnvVarNames?: Map<string, string>
): Promise<ExecutionResult> {
let outputs: Map<string, string> = new Map<string, string>();
const summaries: string[] = [];
if (!outputEnvVarNames) {
const error = new OutputEnvironmentVariableUndefinedError(actionName);
context.logProvider?.error(
getLocalizedString(logMessageKeys.failExecuteDriver, actionName, error.displayMessage)
);
return {
result: err(error),
summaries: summaries,
};
}

const speContainerTypeState: CreateSPEContainerTypeOutput = loadStateFromEnv(outputEnvVarNames);

try {
context.logProvider?.info(getLocalizedString(logMessageKeys.startExecuteDriver, actionName));
this.validateArgs(args);

if (!speContainerTypeState.containerTypeId) {
context.logProvider?.info(
getLocalizedString(
logMessageKeys.startCreateContainerType,
outputEnvVarNames.get(OutputKeys.containerTypeId)
)
);

const speContainerTypeClient = new SPEContainerTypeAppClient(
context.m365TokenProvider,
context.logProvider
);

const speContainerType = await speContainerTypeClient.createSPEContainerType(
args.owningApplicationId,
args.billingClassification,
args.name,
args.discoverable
);

speContainerTypeState.containerTypeId = speContainerType.id;
outputs = mapStateToEnv(speContainerTypeState, outputEnvVarNames);

const summary = getLocalizedString(
logMessageKeys.successCreateContainerType,
speContainerType.id
);

context.logProvider?.info(summary);
summaries.push(summary);
} else {
context.logProvider?.info(
getLocalizedString(
logMessageKeys.skipCreateContainerType,
outputEnvVarNames.get(OutputKeys.containerTypeId)
)
);
}
return {
result: ok(outputs),
summaries: summaries,
};
} catch (error) {
if (error instanceof UserError || error instanceof SystemError) {
context.logProvider?.error(
getLocalizedString(logMessageKeys.failExecuteDriver, actionName, error.displayMessage)
);
return {
result: err(error),
summaries: summaries,
};
}

if (axios.isAxiosError(error)) {
const message = JSON.stringify(error.response!.data);
context.logProvider?.error(
getLocalizedString(logMessageKeys.failExecuteDriver, actionName, message)
);
return {
result: err(new HttpServerError(error, actionName, message)),
summaries: summaries,
};
}
const message = JSON.stringify(error);
context.logProvider?.error(
getLocalizedString(logMessageKeys.failExecuteDriver, actionName, message)
);

return {
result: err(assembleError(error as Error, actionName)),
summaries: summaries,
};
}
}

private validateArgs(args: CreateSPEContainerTypeArgs): void {
const invalidParameters: string[] = [];
if (typeof args.name !== "string" || !args.name) {
invalidParameters.push("name");
}

if (typeof args.owningApplicationId !== "string" || !args.owningApplicationId) {
invalidParameters.push("owningApplicationId");
}

if (
(args.billingClassification && typeof args.billingClassification !== "string") ||
!Object.values(SPContainerTypeBillingClassification).includes(args.billingClassification)
) {
invalidParameters.push("billingClassification");
}

if (typeof args.discoverable !== "boolean") {
invalidParameters.push("discoverable");
}

if (invalidParameters.length > 0) {
throw new InvalidActionInputError(actionName, invalidParameters);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { SPContainerTypeBillingClassification } from "./sharePointEmbeddedContainerType";

export interface CreateSPEContainerTypeArgs {
owningApplicationId: string; // The application ID of the Microsoft Entra app that will own the container type;
billingClassification: SPContainerTypeBillingClassification; // Billing classification for the container type;
name?: string; // The name for the container type;
discoverable?: boolean; // Whether the container type is discoverable by M365 apps, including Copilot;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

export type CreateSPEContainerTypeOutput = {
containerTypeId?: string;
};

// The const is used to reference the property name in CreateAadAppOutput. When renaming the properties in CreateAadAppOutput, you need to update the const as well.
export const OutputKeys = {
containerTypeId: "containerTypeId",
};
Loading