├── .deployment
├── global.json
├── Source
├── RemoteSupport
│ ├── ClientApp
│ │ ├── src
│ │ │ ├── react-app-env.d.ts
│ │ │ ├── models
│ │ │ │ └── on-call-support-detail.ts
│ │ │ ├── index.tsx
│ │ │ ├── router
│ │ │ │ └── router.tsx
│ │ │ ├── constants
│ │ │ │ └── constants.ts
│ │ │ ├── app.tsx
│ │ │ ├── styles
│ │ │ │ └── site.css
│ │ │ ├── api
│ │ │ │ ├── axios-decorator.ts
│ │ │ │ └── remote-support-api.ts
│ │ │ └── components
│ │ │ │ └── error-page.tsx
│ │ ├── tslint.json
│ │ ├── public
│ │ │ └── index.html
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── wwwroot
│ │ ├── images
│ │ │ ├── Urgent.png
│ │ │ └── AppIcon.png
│ │ └── Artifacts
│ │ │ ├── Urgent.png
│ │ │ └── AppIcon.png
│ ├── Models
│ │ ├── AdaptiveChoiceSet.cs
│ │ ├── JwtClaims.cs
│ │ ├── ErrorResponse.cs
│ │ ├── InputChoiceSet.cs
│ │ └── AdaptiveCardAction.cs
│ ├── Helpers
│ │ ├── ITokenHelper.cs
│ │ ├── TeamMemberCacheHelper.cs
│ │ └── TokenHelper.cs
│ ├── Bot
│ │ ├── RemoteSupportActivityHandlerOptions.cs
│ │ ├── RemoteSupportAdapterWithErrorHandler.cs
│ │ ├── BotLocalizationCultureProvider.cs
│ │ └── RemoteSupportActivityMiddleWare.cs
│ ├── Cards
│ │ ├── CardConstants.cs
│ │ ├── WithdrawCard.cs
│ │ ├── WelcomeTeamCard.cs
│ │ └── WelcomeCard.cs
│ ├── appsettings.json
│ ├── Controllers
│ │ ├── BotController.cs
│ │ ├── ResourceController.cs
│ │ └── BaseRemoteSupportController.cs
│ ├── Program.cs
│ └── Startup.cs
├── RemoteSupport.Configuration
│ ├── ClientApp
│ │ ├── src
│ │ │ ├── react-app-env.d.ts
│ │ │ ├── index.tsx
│ │ │ ├── constants.ts
│ │ │ ├── router
│ │ │ │ └── router.tsx
│ │ │ ├── app.tsx
│ │ │ ├── adal-config.tsx
│ │ │ ├── components
│ │ │ │ ├── radiobutton-preview.tsx
│ │ │ │ ├── choiceset-preview.tsx
│ │ │ │ ├── input-text-preview.tsx
│ │ │ │ ├── datepicker-preview.tsx
│ │ │ │ ├── checkbox-preview.tsx
│ │ │ │ ├── datepicker-form.tsx
│ │ │ │ └── input-text-form.tsx
│ │ │ ├── styles
│ │ │ │ └── theme.css
│ │ │ └── api
│ │ │ │ ├── axios-decorator.ts
│ │ │ │ └── incident-api.ts
│ │ ├── tslint.json
│ │ ├── tsconfig.json
│ │ ├── public
│ │ │ └── index.html
│ │ └── package.json
│ ├── appsettings.Development.json
│ ├── Models
│ │ └── AzureAdSettings.cs
│ ├── appsettings.json
│ ├── Program.cs
│ ├── Controllers
│ │ └── SettingsController.cs
│ ├── LocalizationCultureProvider.cs
│ └── Microsoft.Teams.Apps.RemoteSupport.Configuration.csproj
├── RemoteSupport.Common
│ ├── Models
│ │ ├── Configuration
│ │ │ ├── TokenOptions.cs
│ │ │ ├── StorageOptions.cs
│ │ │ └── SearchServiceOptions.cs
│ │ ├── TicketSeverity.cs
│ │ ├── AzureAdClaimTypes.cs
│ │ ├── TicketIdGenerator.cs
│ │ ├── TicketStatus.cs
│ │ ├── AdaptiveCardPlaceHolderMapper.cs
│ │ ├── TicketSearchScope.cs
│ │ ├── OnCallSMEDetail.cs
│ │ ├── OnCallExpertsDetail.cs
│ │ ├── ChangeTicketStatus.cs
│ │ ├── CardConfigurationEntity.cs
│ │ └── OnCallSupportDetail.cs
│ ├── Providers
│ │ ├── ITicketIdGeneratorStorageProvider.cs
│ │ ├── IOnCallSupportDetailSearchService.cs
│ │ ├── ITicketDetailStorageProvider.cs
│ │ ├── ITicketSearchService.cs
│ │ ├── IOnCallSupportDetailStorageProvider.cs
│ │ ├── ICardConfigurationStorageProvider.cs
│ │ ├── TicketDetailStorageProvider.cs
│ │ ├── StorageBaseProvider.cs
│ │ └── OnCallSupportDetailStorageProvider.cs
│ ├── Utility
│ │ └── Utility.cs
│ └── Microsoft.Teams.Apps.RemoteSupport.Common.csproj
└── Microsoft.Teams.Apps.RemoteSupport.sln
├── Manifest
├── SME
│ ├── color.png
│ ├── outline.png
│ ├── zh-CN.json
│ ├── zh-TW.json
│ ├── ko.json
│ ├── ja.json
│ ├── he.json
│ ├── ar.json
│ ├── en.json
│ ├── ru.json
│ ├── es.json
│ ├── de.json
│ ├── pt-BR.json
│ ├── fr.json
│ └── manifest.json
└── EndUser
│ ├── color.png
│ ├── outline.png
│ ├── zh-CN.json
│ ├── zh-TW.json
│ ├── ja.json
│ ├── ko.json
│ ├── he.json
│ ├── en.json
│ ├── ar.json
│ ├── ru.json
│ ├── es.json
│ ├── de.json
│ ├── pt-BR.json
│ ├── fr.json
│ └── manifest.json
├── Build
└── stylecop.json
├── deploy.cmd
├── CODE_OF_CONDUCT.md
├── LICENSE
├── SECURITY.md
├── deploy.bot.cmd
└── deploy.configuration.cmd
/.deployment:
--------------------------------------------------------------------------------
1 | [config]
2 | command = deploy.cmd
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "2.1.515"
4 | }
5 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/Manifest/SME/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Manifest/SME/color.png
--------------------------------------------------------------------------------
/Manifest/SME/outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Manifest/SME/outline.png
--------------------------------------------------------------------------------
/Manifest/EndUser/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Manifest/EndUser/color.png
--------------------------------------------------------------------------------
/Manifest/EndUser/outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Manifest/EndUser/outline.png
--------------------------------------------------------------------------------
/Source/RemoteSupport/wwwroot/images/Urgent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Source/RemoteSupport/wwwroot/images/Urgent.png
--------------------------------------------------------------------------------
/Source/RemoteSupport/wwwroot/Artifacts/Urgent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Source/RemoteSupport/wwwroot/Artifacts/Urgent.png
--------------------------------------------------------------------------------
/Source/RemoteSupport/wwwroot/images/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Source/RemoteSupport/wwwroot/images/AppIcon.png
--------------------------------------------------------------------------------
/Source/RemoteSupport/wwwroot/Artifacts/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-remotesupport/HEAD/Source/RemoteSupport/wwwroot/Artifacts/AppIcon.png
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Build/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "Microsoft"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/deploy.cmd:
--------------------------------------------------------------------------------
1 | @if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off
2 |
3 | IF "%SITE_ROLE%" == "bot" (
4 | deploy.bot.cmd
5 | ) ELSE (
6 | IF "%SITE_ROLE%" == "configuration" (
7 | deploy.configuration.cmd
8 | )ELSE (
9 | echo You have to set SITE_ROLE setting to "bot"
10 | exit /b 1
11 | )
12 | )
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/models/on-call-support-detail.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | export class OnCallSupportDetail {
8 | ModifiedByName: string | "" = "";
9 | ModifiedByObjectId?: string | null = null;
10 | ModifiedOn: Date | null = null;
11 | OnCallSMEs: string | "" = "";
12 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint-react"
5 | ],
6 | "linterOptions": {
7 | "exclude": [
8 | "node_modules/**/*.ts"
9 | ]
10 | },
11 | "rules": {
12 | "jsx-no-lambda": false,
13 | "member-access": false,
14 | "no-console": false,
15 | "ordered-imports": false,
16 | "quotemark": false,
17 | "semicolon": false
18 | },
19 | "rulesDirectory": [
20 | ]
21 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import * as React from "react";
8 | import * as ReactDOM from "react-dom";
9 | import { BrowserRouter as Router } from "react-router-dom";
10 | import App from "./app";
11 |
12 |
13 | ReactDOM.render(
14 |
15 |
16 | , document.getElementById("root"));
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint-react"
5 | ],
6 | "linterOptions": {
7 | "exclude": [
8 | "node_modules/**/*.ts"
9 | ]
10 | },
11 | "rules": {
12 | "jsx-no-lambda": false,
13 | "member-access": false,
14 | "no-console": false,
15 | "ordered-imports": false,
16 | "quotemark": false,
17 | "semicolon": false
18 | },
19 | "rulesDirectory": [
20 | ]
21 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React from "react";
8 | import ReactDOM from "react-dom";
9 | import { BrowserRouter as Router } from "react-router-dom";
10 | import App from "./app";
11 | import { getAzureActiveDirectorySettingsAsync } from "./api/incident-api";
12 |
13 | ReactDOM.render(
14 |
15 |
16 | , document.getElementById("root"));
17 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/constants.ts:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | export const DarkTheme: string = "dark";
6 | export const ContrastTheme: string = "contrast";
7 |
8 | export const isNullorWhiteSpace = (input: string): boolean => {
9 | return !input || !input.trim();
10 | }
11 |
12 | export enum userControls {
13 | inputText = 1,
14 | dropDown = 2,
15 | inputDate = 3,
16 | radioButton = 4,
17 | checkBox = 5
18 | }
19 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Remote Support
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/Configuration/TokenOptions.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Provides application setting related to JWT token.
9 | ///
10 | public class TokenOptions
11 | {
12 | ///
13 | /// Gets or sets random key to create JWT security key.
14 | ///
15 | public string SecurityKey { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/Configuration/StorageOptions.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Provides application setting related to Azure Table Storage.
9 | ///
10 | public class StorageOptions
11 | {
12 | ///
13 | /// Gets or sets Azure Table Storage connection string.
14 | ///
15 | public string ConnectionString { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/router/router.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import * as React from "react";
8 | import { BrowserRouter, Route, Switch } from "react-router-dom";
9 | import Home from "../components/home";
10 |
11 | const AppRoute = () => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | export default AppRoute;
21 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Models/AdaptiveChoiceSet.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Models
6 | {
7 | ///
8 | /// AdaptiveChoice
9 | ///
10 | public class AdaptiveChoiceSet
11 | {
12 | ///
13 | /// Gets or sets title
14 | ///
15 | public string Title { get; set; }
16 |
17 | ///
18 | /// Gets or sets value
19 | ///
20 | public string Value { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Models/JwtClaims.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Models
6 | {
7 | ///
8 | /// Claims which are added in JWT token.
9 | ///
10 | public class JwtClaims
11 | {
12 | ///
13 | /// Gets or sets activity Id.
14 | ///
15 | public string FromId { get; set; }
16 |
17 | ///
18 | /// Gets or sets service URL of bot.
19 | ///
20 | public string ApplicationBasePath { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/router/router.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import * as React from "react";
8 | import { BrowserRouter, Route, Switch } from "react-router-dom";
9 | import ManageExperts from '../components/manage-experts';
10 | import ErrorPage from '../components/error-page';
11 |
12 | export const AppRoute: React.FunctionComponent<{}> = () => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | };
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noImplicitAny": false,
20 | "noEmit": true,
21 | "jsx": "react",
22 | "noFallthroughCasesInSwitch": true
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext",
8 | "ES2015"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noImplicitAny": false,
21 | "noEmit": true,
22 | "jsx": "react",
23 | "noFallthroughCasesInSwitch": true
24 | },
25 | "include": [
26 | "src"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/TicketSeverity.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Represents the current severity of ticket.
9 | ///
10 | public enum TicketSeverity
11 | {
12 | ///
13 | /// Represents that ticket needs to be addressed on normal priority.
14 | ///
15 | Normal = 0,
16 |
17 | ///
18 | /// Represents that ticket needs to be addressed on high priority.
19 | ///
20 | Urgent = 1,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/AzureAdClaimTypes.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Azure Active Directory Claim Types.
9 | ///
10 | public static class AzureAdClaimTypes
11 | {
12 | ///
13 | /// Object Identifier.
14 | ///
15 | public const string ObjectId = "http://schemas.microsoft.com/identity/claims/objectidentifier";
16 |
17 | ///
18 | /// Identity Scope.
19 | ///
20 | public const string Scope = "http://schemas.microsoft.com/identity/claims/scope";
21 | }
22 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Models/ErrorResponse.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Models
6 | {
7 | using Newtonsoft.Json;
8 |
9 | ///
10 | /// Error response class.
11 | ///
12 | public class ErrorResponse
13 | {
14 | ///
15 | /// Gets or sets error status code.
16 | ///
17 | [JsonProperty("code")]
18 | public string StatusCode { get; set; }
19 |
20 | ///
21 | /// Gets or sets error message.
22 | ///
23 | [JsonProperty("message")]
24 | public string ErrorMessage { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/constants/constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | export default class Constants {
8 |
9 | //Commands
10 | public static readonly updateExpertListCommand: string = "UPDATE EXPERT LIST";
11 |
12 | //Themes
13 | public static readonly body: string = "body";
14 | public static readonly theme: string = "theme";
15 | public static readonly default: string = "default";
16 | public static readonly light: string = "light";
17 | public static readonly dark: string = "dark";
18 | public static readonly contrast: string = "contrast";
19 |
20 | //KeyCodes
21 | public static readonly keyCodeEnter: number = 13;
22 | public static readonly keyCodeSpace: number = 32;
23 |
24 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "远程支持",
4 | "description.short": "帮助请求支持并快速与专家联系",
5 | "description.full": "快速请求远程支持。搜索请求并通过群组聊天对紧急请求上报给呼叫专家。",
6 | "bots[0].commandLists[0].commands[0].title": "新建请求",
7 | "bots[0].commandLists[0].commands[0].description": "向呼叫团队提出请求",
8 | "composeExtensions[0].commands[0].title": "待处理",
9 | "composeExtensions[0].commands[0].description": "搜索活动请求",
10 | "composeExtensions[0].commands[0].parameters[0].title": "搜索",
11 | "composeExtensions[0].commands[0].parameters[0].description": "搜索请求",
12 | "composeExtensions[0].commands[1].title": "已关闭",
13 | "composeExtensions[0].commands[1].description": "搜索已关闭的请求",
14 | "composeExtensions[0].commands[1].parameters[0].title": "搜索",
15 | "composeExtensions[0].commands[1].parameters[0].description": "搜索请求"
16 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/zh-TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "遠端支援",
4 | "description.short": "協助要求支援並儘快與專家連線",
5 | "description.full": "快速要求遠端支援。搜尋要求,並透過群組聊天進行緊急要求,向待命專家呈報。",
6 | "bots[0].commandLists[0].commands[0].title": "新增要求",
7 | "bots[0].commandLists[0].commands[0].description": "向待命小組提出要求",
8 | "composeExtensions[0].commands[0].title": "使用中",
9 | "composeExtensions[0].commands[0].description": "搜尋使用中的要求",
10 | "composeExtensions[0].commands[0].parameters[0].title": "搜尋",
11 | "composeExtensions[0].commands[0].parameters[0].description": "搜尋要求",
12 | "composeExtensions[0].commands[1].title": "已關閉",
13 | "composeExtensions[0].commands[1].description": "搜尋已關閉的要求",
14 | "composeExtensions[0].commands[1].parameters[0].title": "搜尋",
15 | "composeExtensions[0].commands[1].parameters[0].description": "搜尋要求"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/TicketIdGenerator.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using Microsoft.WindowsAzure.Storage.Table;
8 |
9 | ///
10 | /// Class contains latest ticket Id details.
11 | ///
12 | public class TicketIdGenerator : TableEntity
13 | {
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | public TicketIdGenerator()
18 | {
19 | this.PartitionKey = Constants.TicketIdGeneratorPartitionKey;
20 | }
21 |
22 | ///
23 | /// Gets or sets ticket id.
24 | ///
25 | public int MaxTicketId { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/app.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 |
8 | import * as React from "react";
9 | import { AppRoute } from "./router/router";
10 | import { Provider, themes } from "@fluentui/react";
11 |
12 | export interface IAppState {
13 | theme: string;
14 | themeStyle: any;
15 | }
16 |
17 | export default class App extends React.Component<{}, IAppState> {
18 |
19 | constructor(props: any) {
20 | super(props);
21 | this.state = {
22 | theme: "",
23 | themeStyle: themes.teams,
24 | }
25 | }
26 |
27 | /**
28 | * Renders the component
29 | */
30 | public render(): JSX.Element {
31 |
32 | return (
33 |
34 |
37 |
38 | );
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Helpers/ITokenHelper.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Helpers
6 | {
7 | ///
8 | /// Helper for custom JWT token generation and retrieval of user access token.
9 | ///
10 | public interface ITokenHelper
11 | {
12 | ///
13 | /// Generate JWT token used by client application to authenticate HTTP calls with API.
14 | ///
15 | /// Service URL from bot.
16 | /// Unique Id from activity.
17 | /// Expiry of token.
18 | /// JWT token.
19 | string GenerateAPIAuthToken(string applicationBasePath, string fromId, int jwtExpiryMinutes);
20 | }
21 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/app.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import * as React from "react";
8 | import ReactDOM from "react-dom";
9 | import AppRoute from "./router/router";
10 | import { runWithAdal } from 'react-adal';
11 | import { authContext } from './adal-config';
12 |
13 | export default class App extends React.Component<{}, {}> {
14 | constructor(props: any) {
15 | super(props);
16 | }
17 |
18 | render(): JSX.Element {
19 | return (
20 |
23 | );
24 | }
25 | }
26 |
27 | /* renders the component */
28 | const DO_NOT_LOGIN = false;
29 |
30 | runWithAdal(authContext, () => {
31 | ReactDOM.render(
32 | , document.getElementById("container"));
33 | }, DO_NOT_LOGIN);
--------------------------------------------------------------------------------
/Manifest/EndUser/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "リモート サポート",
4 | "description.short": "サポート要求をし、すばやくエキスパートに連絡するのに役立ちます",
5 | "description.full": "リモートサポートをすばやく要求します。リクエストを検索し、緊急対応のためにグループ チャットで通話のエキスパートにエスカレートします。",
6 | "bots[0].commandLists[0].commands[0].title": "新しい要求",
7 | "bots[0].commandLists[0].commands[0].description": "通話チームへの要求の作成",
8 | "composeExtensions[0].commands[0].title": "アクティブ",
9 | "composeExtensions[0].commands[0].description": "アクティブな要求の検索",
10 | "composeExtensions[0].commands[0].parameters[0].title": "検索",
11 | "composeExtensions[0].commands[0].parameters[0].description": "要求の検索",
12 | "composeExtensions[0].commands[1].title": "終了",
13 | "composeExtensions[0].commands[1].description": "終了した要求の検索",
14 | "composeExtensions[0].commands[1].parameters[0].title": "検索",
15 | "composeExtensions[0].commands[1].parameters[0].description": "要求の検索"
16 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "원격 지원",
4 | "description.short": "지원을 요청하고 전문가와 빠르게 연결할 수 있도록 지원",
5 | "description.full": "원격 지원을 빠르게 요청할 수 있하도록 요청합니다. 긴급 요청에 대한 그룹 채팅을 통해 통화 전문가로 검색 요청 및 검색 요청에 에스컬레이션합니다.",
6 | "bots[0].commandLists[0].commands[0].title": "새 요청",
7 | "bots[0].commandLists[0].commands[0].description": "대기 중인 팀에 요청",
8 | "composeExtensions[0].commands[0].title": "활성",
9 | "composeExtensions[0].commands[0].description": "활성 요청 검색",
10 | "composeExtensions[0].commands[0].parameters[0].title": "검색",
11 | "composeExtensions[0].commands[0].parameters[0].description": "검색 요청",
12 | "composeExtensions[0].commands[1].title": "닫힘",
13 | "composeExtensions[0].commands[1].description": "닫힌 요청 검색",
14 | "composeExtensions[0].commands[1].parameters[0].title": "검색",
15 | "composeExtensions[0].commands[1].parameters[0].description": "검색 요청"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/Models/AzureAdSettings.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Configuration.Models
6 | {
7 | ///
8 | /// Azure AD configuration model.
9 | ///
10 | public class AzureAdSettings
11 | {
12 | ///
13 | /// Gets or sets Client Id.
14 | ///
15 | public string ClientId { get; set; }
16 |
17 | ///
18 | /// Gets or sets the Azure AD instance.
19 | ///
20 | public string Instance { get; set; }
21 |
22 | ///
23 | /// Gets or sets Tenant Id.
24 | ///
25 | public string Tenant { get; set; }
26 |
27 | ///
28 | /// Gets or sets User Principal Name.
29 | ///
30 | public string Upn { get; set; }
31 | }
32 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/TicketStatus.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Represents the current status of a ticket.
9 | ///
10 | public enum TicketState
11 | {
12 | ///
13 | /// Represents an open ticket which requires further action.
14 | ///
15 | Unassigned = 0,
16 |
17 | ///
18 | /// Represents a ticket which is assigned to SME for further action.
19 | ///
20 | Assigned = 1,
21 |
22 | ///
23 | /// Represents a ticket that requires no further action.
24 | ///
25 | Closed = 2,
26 |
27 | ///
28 | /// Represents a ticket that is canceled by user.
29 | ///
30 | Withdrawn = 3,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Manifest/EndUser/he.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "תמיכה מרחוק",
4 | "description.short": "עוזר לבקש תמיכה ולהתחבר במהירות למומחים",
5 | "description.full": "בקש תמיכה מרחוק במהירות. חפש בבקשות והפנה את בקשתך למומחים הזמינים באמצעות צ'אט קבוצתי המשמש לבקשות דחופות.",
6 | "bots[0].commandLists[0].commands[0].title": "בקשה חדשה",
7 | "bots[0].commandLists[0].commands[0].description": "צור בקשה לצוות הזמין",
8 | "composeExtensions[0].commands[0].title": "פעיל",
9 | "composeExtensions[0].commands[0].description": "חפש בקשות פעילות",
10 | "composeExtensions[0].commands[0].parameters[0].title": "חפש",
11 | "composeExtensions[0].commands[0].parameters[0].description": "חיפוש בקשות",
12 | "composeExtensions[0].commands[1].title": "סגור",
13 | "composeExtensions[0].commands[1].description": "חיפוש בקשות שנסגרו",
14 | "composeExtensions[0].commands[1].parameters[0].title": "חפש",
15 | "composeExtensions[0].commands[1].parameters[0].description": "חיפוש בקשות"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 | Card Configuration
10 |
11 |
12 |
15 |
18 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Manifest/EndUser/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Remote Support",
4 | "description.short": "Helps request support and connect with experts quickly",
5 | "description.full": "Request remote support quickly. Search requests and escalate to on call experts via group chat for urgent requests.",
6 | "bots[0].commandLists[0].commands[0].title": "New request",
7 | "bots[0].commandLists[0].commands[0].description": "Make a request to the on-call team",
8 | "composeExtensions[0].commands[0].title": "Active",
9 | "composeExtensions[0].commands[0].description": "Search active requests",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Search",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Search requests",
12 | "composeExtensions[0].commands[1].title": "Closed",
13 | "composeExtensions[0].commands[1].description": "Search closed requests",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Search",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Search requests"
16 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "دعم عن بعد",
4 | "description.short": "يساعد على طلب الدعم والتواصل مع الخبراء سريعاً",
5 | "description.full": "يمكنك طلب دعم عن بعد سريعاً والبحث في الطلبات وتصعيد الطلبات إلى الخبراء المتوفرين عند الطلب عبر الدردشة الجماعية للطلبات العاجلة.",
6 | "bots[0].commandLists[0].commands[0].title": "طلب جديد",
7 | "bots[0].commandLists[0].commands[0].description": "إنشاء طلب وإرساله إلى الفريق المتوفر عند الطلب",
8 | "composeExtensions[0].commands[0].title": "النشطة",
9 | "composeExtensions[0].commands[0].description": "بحث في الطلبات النشطة",
10 | "composeExtensions[0].commands[0].parameters[0].title": "بحث",
11 | "composeExtensions[0].commands[0].parameters[0].description": "بحث في الطلبات",
12 | "composeExtensions[0].commands[1].title": "المغلقة",
13 | "composeExtensions[0].commands[1].description": "بحث في الطلبات المغلقة",
14 | "composeExtensions[0].commands[1].parameters[0].title": "بحث",
15 | "composeExtensions[0].commands[1].parameters[0].description": "بحث في الطلبات"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/AdaptiveCardPlaceHolderMapper.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using Newtonsoft.Json;
8 |
9 | ///
10 | /// Class maps controls from json card file to adaptive card.
11 | ///
12 | public class AdaptiveCardPlaceHolderMapper
13 | {
14 | ///
15 | /// Gets or sets unique identifier of the control.
16 | ///
17 | [JsonProperty("id")]
18 | public string Id { get; set; }
19 |
20 | ///
21 | /// Gets or sets type of control to be shown on adaptive card.
22 | ///
23 | [JsonProperty("type")]
24 | public string InputType { get; set; }
25 |
26 | ///
27 | /// Gets or sets displayName of control to be shown on adaptive card.
28 | ///
29 | [JsonProperty("displayName")]
30 | public string DisplayName { get; set; }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/adal-config.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import { AuthenticationContext, adalFetch, withAdalLogin, AdalConfig } from 'react-adal';
8 | import { getAzureActiveDirectorySettings } from "./api/incident-api";
9 |
10 | getAzureActiveDirectorySettings();
11 |
12 | export const adalConfig: AdalConfig = {
13 | tenant: localStorage.getItem("TenantId")!,
14 | clientId: localStorage.getItem("ClientId")!,
15 | endpoints: {
16 | api: localStorage.getItem("TokenEndpoint")!,
17 | },
18 | postLogoutRedirectUri: window.location.origin,
19 | cacheLocation: 'localStorage'
20 | };
21 |
22 | export const authContext = new AuthenticationContext(adalConfig);
23 | export const getToken = () => authContext.getCachedToken(adalConfig.clientId);
24 | export const adalApiFetch = (fetch:any, url:any, options:any) =>
25 | adalFetch(authContext, adalConfig!.endpoints!.api, fetch, url, options);
26 |
27 | export const withAdalLoginApi = withAdalLogin(authContext, adalConfig!.endpoints!.api);
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/Configuration/SearchServiceOptions.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Provides settings related to SearchService.
9 | ///
10 | public class SearchServiceOptions
11 | {
12 | ///
13 | /// Gets or sets search service name.
14 | ///
15 | public string SearchServiceName { get; set; }
16 |
17 | ///
18 | /// Gets or sets search service query API key.
19 | ///
20 | public string SearchServiceQueryApiKey { get; set; }
21 |
22 | ///
23 | /// Gets or sets search service admin API key.
24 | ///
25 | public string SearchServiceAdminApiKey { get; set; }
26 |
27 | ///
28 | /// Gets or sets search indexing interval in minutes.
29 | ///
30 | public string SearchIndexingIntervalInMinutes { get; set; }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Manifest/EndUser/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Удаленная поддержка",
4 | "description.short": "Помогает запрашивать поддержку и быстро связываться с экспертами",
5 | "description.full": "Быстро запрашивайте удаленную поддержку. Находите запросы и используйте эскалацию срочных запросов дежурным экспертам посредством группового чата.",
6 | "bots[0].commandLists[0].commands[0].title": "Новый запрос",
7 | "bots[0].commandLists[0].commands[0].description": "Создание запроса дежурной команде",
8 | "composeExtensions[0].commands[0].title": "Активно",
9 | "composeExtensions[0].commands[0].description": "Поиск активных запросов",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Поиск",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Поиск запросов",
12 | "composeExtensions[0].commands[1].title": "Закрыто",
13 | "composeExtensions[0].commands[1].description": "Поиск закрытых запросов",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Поиск",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Поиск запросов"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information"
5 | }
6 | },
7 | "ApplicationInsights": {
8 | "InstrumentationKey": "",
9 | "LogLevel": {
10 | "Default": "Information",
11 | "Microsoft": "Information"
12 | }
13 | },
14 | "AllowedHosts": "*",
15 | "Storage": {
16 | "ConnectionString": ""
17 | },
18 | "Bot": {
19 | "TeamLink": ""
20 | },
21 | "Card": {
22 | "DefaultCardTemplate": "[{\"type\": \"Input.Date\",\"id\": \"First observed on_IssueOccuredOn\",\"max\":\"_maxDate_\",\"value\": \"_issueDate_\"},{\"type\": \"TextBlock\",\"id\": \"Validation Message_ValidationMessage\",\"text\": \"_dateValidationText_\",\"spacing\": \"None\",\"color\": \"Attention\",\"isVisible\": \"_showDateValidation_\"}]"
23 | },
24 | "AzureAd": {
25 | "ClientId": "",
26 | "Instance": "",
27 | "Tenant": "",
28 | "Upn": ""
29 | },
30 | "i18n": {
31 | "DefaultCulture": "en",
32 | "SupportedCultures": "en,ar,de,es,fr,he,ja,ko,pt-BR,ru,zh-CN,zh-TW"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Manifest/SME/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "远程支持 - 专家",
4 | "description.short": "专业团队自动程序,可处理用户请求并安排呼叫专家。",
5 | "description.full": "管理支持请求,并设置团队中的呼叫专家。请专家团队立即搜索紧急、已分配和未分配的请求。",
6 | "bots[0].commandLists[0].commands[0].title": "专家列表",
7 | "bots[0].commandLists[0].commands[0].description": "呼叫专家列表",
8 | "composeExtensions[0].commands[0].title": "紧急",
9 | "composeExtensions[0].commands[0].description": "搜索紧急请求",
10 | "composeExtensions[0].commands[0].parameters[0].title": "搜索",
11 | "composeExtensions[0].commands[0].parameters[0].description": "搜索请求",
12 | "composeExtensions[0].commands[1].title": "已分配",
13 | "composeExtensions[0].commands[1].description": "分配给专家的搜索请求",
14 | "composeExtensions[0].commands[1].parameters[0].title": "搜索",
15 | "composeExtensions[0].commands[1].parameters[0].description": "搜索请求",
16 | "composeExtensions[0].commands[2].title": "未分配",
17 | "composeExtensions[0].commands[2].description": "搜索尚未分配给专家的未分配请求",
18 | "composeExtensions[0].commands[2].parameters[0].title": "搜索",
19 | "composeExtensions[0].commands[2].parameters[0].description": "搜索请求"
20 | }
--------------------------------------------------------------------------------
/Manifest/SME/zh-TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "遠端支援 - 專家",
4 | "description.short": "可處理使用者要求並設定待命專家的專家小組 Bot。",
5 | "description.full": "管理支援要求,並設定小組中的待命專家。直接從專家小組搜尋緊急、已指派及未指派的要求。",
6 | "bots[0].commandLists[0].commands[0].title": "專家清單",
7 | "bots[0].commandLists[0].commands[0].description": "待命專家清單",
8 | "composeExtensions[0].commands[0].title": "緊急",
9 | "composeExtensions[0].commands[0].description": "搜尋緊急要求",
10 | "composeExtensions[0].commands[0].parameters[0].title": "搜尋",
11 | "composeExtensions[0].commands[0].parameters[0].description": "搜尋要求",
12 | "composeExtensions[0].commands[1].title": "已指派",
13 | "composeExtensions[0].commands[1].description": "搜尋已指派給專家的要求",
14 | "composeExtensions[0].commands[1].parameters[0].title": "搜尋",
15 | "composeExtensions[0].commands[1].parameters[0].description": "搜尋要求",
16 | "composeExtensions[0].commands[2].title": "未指派",
17 | "composeExtensions[0].commands[2].description": "搜尋尚未指派給專家的未指派要求",
18 | "composeExtensions[0].commands[2].parameters[0].title": "搜尋",
19 | "composeExtensions[0].commands[2].parameters[0].description": "搜尋要求"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/TicketSearchScope.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | ///
8 | /// Class represents scope on basis of which tickets will be searched in messaging extension.
9 | ///
10 | public enum TicketSearchScope
11 | {
12 | ///
13 | /// Tickets with high priority.
14 | ///
15 | UrgentTickets,
16 |
17 | ///
18 | /// Tickets assigned to a subject-matter expert.
19 | ///
20 | AssignedTickets,
21 |
22 | ///
23 | /// Tickets which are not assigned to subject-matter expert.
24 | ///
25 | UnassignedTickets,
26 |
27 | ///
28 | /// Tickets which are active to subject-matter expert.
29 | ///
30 | ActiveTickets,
31 |
32 | ///
33 | /// Tickets which are not closed to subject-matter expert.
34 | ///
35 | ClosedTickets,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/ITicketIdGeneratorStorageProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Threading.Tasks;
8 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
9 |
10 | ///
11 | /// Interface to generate ticket ids from table storage.
12 | ///
13 | public interface ITicketIdGeneratorStorageProvider
14 | {
15 | ///
16 | /// Get a new ticket id from the table storage.
17 | ///
18 | /// Next TicketId generated from table storage.
19 | Task GetTicketIdAsync();
20 |
21 | ///
22 | /// update the ticket id in the table storage.
23 | ///
24 | /// Entity containing latest ticket Id details.
25 | /// Returns next ticket id generated from table storage.
26 | Task UpdateTicketIdAsync(TicketIdGenerator ticketIdGenerator);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/Manifest/EndUser/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Soporte remoto",
4 | "description.short": "Ayude a solicitar soporte técnico y a conectarse con los expertos rápidamente",
5 | "description.full": "Solicite asistencia remota rápidamente. Busque solicitudes y escale a expertos en llamadas a través de chat grupal para solicitudes urgentes.",
6 | "bots[0].commandLists[0].commands[0].title": "Nueva solicitud",
7 | "bots[0].commandLists[0].commands[0].description": "Realizar una solicitud al equipo de la llamada",
8 | "composeExtensions[0].commands[0].title": "Activo",
9 | "composeExtensions[0].commands[0].description": "Buscar solicitudes activas",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Buscar",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Solicitudes de búsqueda",
12 | "composeExtensions[0].commands[1].title": "Cerrado",
13 | "composeExtensions[0].commands[1].description": "Búsqueda de solicitudes cerradas",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Buscar",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Solicitudes de búsqueda"
16 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Remotesupport",
4 | "description.short": "Hilft, Support anzufordern und sich schnell mit Experten zu verbinden",
5 | "description.full": "Fordern Sie schnell Remotesupport an. Suchen Sie Anfragen und eskalieren Sie bei dringenden Anfragen über den Gruppen-Chat an die Rufbereitschaftsexperten.",
6 | "bots[0].commandLists[0].commands[0].title": "Neue Anforderung",
7 | "bots[0].commandLists[0].commands[0].description": "Eine Anfrage zum Rufbereitschaftsteam tätigen",
8 | "composeExtensions[0].commands[0].title": "Aktiv",
9 | "composeExtensions[0].commands[0].description": "Aktive Anforderungen durchsuchen",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Suchen",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Suchanforderungen",
12 | "composeExtensions[0].commands[1].title": "Geschlossen",
13 | "composeExtensions[0].commands[1].description": "Geschlossene Anforderungen durchsuchen",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Suchen",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Suchanforderungen"
16 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/pt-BR.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Suporte remoto",
4 | "description.short": "Ajuda na solicitação de suporte e se conectar com especialistas rapidamente",
5 | "description.full": "Solicite o suporte remoto rapidamente. Solicitações de pesquisa e escalonar para os especialistas de chamada via chat de grupo para solicitações urgentes.",
6 | "bots[0].commandLists[0].commands[0].title": "Novo pedido",
7 | "bots[0].commandLists[0].commands[0].description": "Faça uma solicitação para a equipe de chamada a pedido",
8 | "composeExtensions[0].commands[0].title": "Ativo",
9 | "composeExtensions[0].commands[0].description": "Pesquisar pedidos ativos",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Pesquisar",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Pedidos de pesquisa",
12 | "composeExtensions[0].commands[1].title": "Fechado",
13 | "composeExtensions[0].commands[1].description": "Pesquisar pedidos fechados",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Pesquisar",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Pedidos de pesquisa"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Bot/RemoteSupportActivityHandlerOptions.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport
6 | {
7 | ///
8 | /// The RemoteSupportActivityHandlerOptions are the options for the bot.
9 | ///
10 | public sealed class RemoteSupportActivityHandlerOptions
11 | {
12 | ///
13 | /// Gets or sets a value indicating whether the response to a message should be all uppercase.
14 | ///
15 | public bool UpperCaseResponse { get; set; }
16 |
17 | ///
18 | /// Gets or sets unique id of Tenant.
19 | ///
20 | public string TenantId { get; set; }
21 |
22 | ///
23 | /// Gets or sets unique identifier of team in which Bot is installed.
24 | ///
25 | public string TeamId { get; set; }
26 |
27 | ///
28 | /// Gets or sets application base Uri.
29 | ///
30 | public string AppBaseUri { get; set; }
31 | }
32 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "remotesupport",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fluentui/react": "^0.43.2",
7 | "@microsoft/applicationinsights-react-js": "^2.5.4",
8 | "@microsoft/teams-js": "^1.10.0",
9 | "axios": "^0.21.1",
10 | "classnames": "^2.3.1",
11 | "react": "^16.14.0",
12 | "react-adal": "^0.5.2",
13 | "react-appinsights": "^3.0.0-rc.6",
14 | "react-dom": "^16.14.0",
15 | "react-router": "^5.2.0",
16 | "react-router-dom": "^5.2.0",
17 | "react-scripts": "^4.0.3",
18 | "typescript": "^3.9.10"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": "react-app"
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | },
41 | "devDependencies": {
42 | "@types/react": "16.9.6",
43 | "@types/react-dom": "16.9.2",
44 | "@types/react-router-dom": "^5.1.7"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/OnCallSMEDetail.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using Newtonsoft.Json;
8 |
9 | ///
10 | /// Class contains details of on call support experts.
11 | ///
12 | public class OnCallSMEDetail
13 | {
14 | ///
15 | /// Gets or sets name of on call expert.
16 | ///
17 | [JsonProperty("name")]
18 | public string Name { get; set; }
19 |
20 | ///
21 | /// Gets or sets Azure Active Directory object Id.
22 | ///
23 | [JsonProperty("objectid")]
24 | public string ObjectId { get; set; }
25 |
26 | ///
27 | /// Gets or sets object Id of on call expert .
28 | ///
29 | [JsonProperty("id")]
30 | public string Id { get; set; }
31 |
32 | ///
33 | /// Gets or sets email Id of on call expert.
34 | ///
35 | [JsonProperty("email")]
36 | public string Email { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/IOnCallSupportDetailSearchService.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Collections.Generic;
8 | using System.Threading.Tasks;
9 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
10 |
11 | ///
12 | /// Interface to provide Search on call support team based on search query.
13 | ///
14 | public interface IOnCallSupportDetailSearchService
15 | {
16 | ///
17 | /// Provide search result for table to be used by SME based on Azure search service.
18 | ///
19 | /// searchQuery to be provided by message extension.
20 | /// Number of search results to return.
21 | /// Number of search results to skip.
22 | /// List of search results.
23 | Task> SearchOnCallSupportTeamAsync(string searchQuery, int? count = null, int? skip = null);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/radiobutton-preview.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React from "react";
8 | import { Flex, Icon, RadioGroup, Text } from '@fluentui/react';
9 | import "../styles/theme.css";
10 |
11 | interface IPreviewProps {
12 | keyVal: number,
13 | displayName: string,
14 | options: Array,
15 | onDeleteComponent: (keyVal: number) => void
16 | }
17 |
18 | export const RadioButtonPreview: React.FunctionComponent = (props) => {
19 | const onDeleteComponent = (keyVal: number) => {
20 | props.onDeleteComponent(keyVal);
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | onDeleteComponent(props.keyVal)} />
32 |
33 | );
34 | }
--------------------------------------------------------------------------------
/Manifest/SME/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "원격 지원 - 전문가",
4 | "description.short": "팀 봇이 사용자 요청을 처리하고 통화 전문가를 설정합니다.",
5 | "description.full": "지원 요청을 관리하고 팀의 상담 전문가에 대해 설정합니다. 전문가 팀의 긴급, 할당 및 할당되지 않은 요청을 바로 검색합니다.",
6 | "bots[0].commandLists[0].commands[0].title": "전문가 목록",
7 | "bots[0].commandLists[0].commands[0].description": "전화 전문가 목록",
8 | "composeExtensions[0].commands[0].title": "긴급",
9 | "composeExtensions[0].commands[0].description": "긴급 요청 검색",
10 | "composeExtensions[0].commands[0].parameters[0].title": "검색",
11 | "composeExtensions[0].commands[0].parameters[0].description": "검색 요청",
12 | "composeExtensions[0].commands[1].title": "할당됨",
13 | "composeExtensions[0].commands[1].description": "전문가에게 할당된 검색 요청",
14 | "composeExtensions[0].commands[1].parameters[0].title": "검색",
15 | "composeExtensions[0].commands[1].parameters[0].description": "검색 요청",
16 | "composeExtensions[0].commands[2].title": "할당 해제됨",
17 | "composeExtensions[0].commands[2].description": "검색되지 않은 요청이 전문가에 아직 할당되지 않았습니다.",
18 | "composeExtensions[0].commands[2].parameters[0].title": "검색",
19 | "composeExtensions[0].commands[2].parameters[0].description": "검색 요청"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Cards/CardConstants.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Cards
6 | {
7 | ///
8 | /// Constants used in bot and task module cards
9 | ///
10 | public static class CardConstants
11 | {
12 | ///
13 | /// Text block card id for first observed on text in remote support request card
14 | ///
15 | public const string IssueOccurredOnId = "IssueOccurredOn";
16 |
17 | ///
18 | /// Text block card id for date time validation message in remote support request card
19 | ///
20 | public const string DateValidationMessageId = "DateValidationMessage";
21 |
22 | ///
23 | /// Date time format to support adaptive card text feature.
24 | ///
25 | ///
26 | /// refer adaptive card text feature https://docs.microsoft.com/en-us/adaptive-cards/authoring-cards/text-features#datetime-formatting-and-localization.
27 | ///
28 | public const string Rfc3339DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Manifest/SME/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "リモート サポート - エキスパート",
4 | "description.short": "ユーザー要求を処理し、通話のエキスパートを設定するためのエキスパート チーム ボットです。",
5 | "description.full": "サポート要求を管理し、チームの通話のエキスパートを設定します。エキスパート チームから直接、緊急、割り当て済み、未割り当ての要求を検索します。",
6 | "bots[0].commandLists[0].commands[0].title": "エキスパートのリスト",
7 | "bots[0].commandLists[0].commands[0].description": "通話のエキスパートのリスト",
8 | "composeExtensions[0].commands[0].title": "緊急",
9 | "composeExtensions[0].commands[0].description": "緊急の要求の検索",
10 | "composeExtensions[0].commands[0].parameters[0].title": "検索",
11 | "composeExtensions[0].commands[0].parameters[0].description": "要求の検索",
12 | "composeExtensions[0].commands[1].title": "割り当て済み",
13 | "composeExtensions[0].commands[1].description": "エキスパートに割り当てられた要求を検索",
14 | "composeExtensions[0].commands[1].parameters[0].title": "検索",
15 | "composeExtensions[0].commands[1].parameters[0].description": "要求の検索",
16 | "composeExtensions[0].commands[2].title": "未割り当て",
17 | "composeExtensions[0].commands[2].description": "エキスパートにまだ割り当てられていない未割り当ての要求を検索する",
18 | "composeExtensions[0].commands[2].parameters[0].title": "検索",
19 | "composeExtensions[0].commands[2].parameters[0].description": "要求の検索"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/styles/site.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | .container-div {
8 | height: 96vh;
9 | padding-left: 3.5rem;
10 | padding-right: 2.5rem;
11 | width: 100vw;
12 | position: fixed;
13 | }
14 |
15 | .container-subdiv {
16 | max-height: 70vh;
17 | overflow-y: auto;
18 | width: auto;
19 | }
20 |
21 | .container-subdiv-main {
22 | max-height: 70vh;
23 | width: auto;
24 | }
25 |
26 | .footer {
27 | position: fixed;
28 | bottom: 0;
29 | right: 0;
30 | width: auto;
31 | padding-bottom: 1.5rem;
32 | padding-left: 2.5rem;
33 | padding-right: 5rem;
34 | align-content: flex-end;
35 | }
36 |
37 | .error {
38 | position: fixed;
39 | bottom: 3.2rem;
40 | width: auto;
41 | padding-bottom: 1.5rem;
42 | padding-right: 5rem;
43 | align-content: flex-end;
44 | }
45 |
46 |
47 | .title {
48 | margin-bottom: 1rem;
49 | margin-top: 1rem;
50 | }
51 |
52 | .margin-button {
53 | margin-left: 3.5rem
54 | }
55 |
56 | .list-title {
57 | margin-bottom: 1rem;
58 | margin-top: 2rem;
59 | }
60 |
61 | .list-width {
62 | width: 38rem
63 | }
64 |
65 | .small-margin-left {
66 | margin-left: 1rem
67 | }
68 |
69 | .error-container {
70 | padding-top: 30vh
71 | }
--------------------------------------------------------------------------------
/Manifest/EndUser/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Support à distance",
4 | "description.short": "Aide à demander un support et à mettre rapidement en relation avec des experts",
5 | "description.full": "Demandez rapidement le support à distance. Recherchez parmi les demandes et faites-les remonter aux experts disponibles via la conversation de groupe en cas de demandes urgentes.",
6 | "bots[0].commandLists[0].commands[0].title": "Nouvelle demande",
7 | "bots[0].commandLists[0].commands[0].description": "Effectuez une demande auprès de l’équipe disponible",
8 | "composeExtensions[0].commands[0].title": "Actives",
9 | "composeExtensions[0].commands[0].description": "Recherchez parmi les demandes actives",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Rechercher",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Recherchez parmi les demandes",
12 | "composeExtensions[0].commands[1].title": "Closes",
13 | "composeExtensions[0].commands[1].description": "Recherchez parmi les demandes closes",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Rechercher",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Recherchez parmi les demandes"
16 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/choiceset-preview.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React from "react";
8 | import { Flex, Icon, Dropdown, Text } from '@fluentui/react';
9 | import "../styles/theme.css";
10 |
11 | interface IPreviewProps {
12 | keyVal: number,
13 | placeholder: string,
14 | displayName : string,
15 | options: Array,
16 | onDeleteComponent: (keyVal: number) => void
17 | }
18 |
19 | export const ChoiceSetPreview: React.FunctionComponent = (props) => {
20 | const onDeleteComponent = (keyVal: number) => {
21 | props.onDeleteComponent(keyVal);
22 | }
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | onDeleteComponent(props.keyVal)} />
33 |
34 | );
35 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/input-text-preview.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React from 'react';
8 | import { Input, Flex, Icon, Text } from '@fluentui/react';
9 | import "../styles/theme.css";
10 |
11 | interface IPreviewProps {
12 | keyVal: number,
13 | placeholder: string,
14 | displayName: string,
15 | onDeleteComponent: (keyVal: number) => void
16 | }
17 |
18 | export const InputTextPreview: React.FunctionComponent = (props) => {
19 | const onDeleteComponent = (keyVal: number) => {
20 | props.onDeleteComponent(keyVal);
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | onDeleteComponent(props.keyVal)} />
32 |
33 | );
34 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/datepicker-preview.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React from "react";
8 | import { Icon, Text, Flex } from '@fluentui/react';
9 | import "../styles/theme.css";
10 |
11 | interface IPreviewProps {
12 | keyVal: number,
13 | displayName : string,
14 | onDeleteComponent: (keyVal: number) => void
15 | }
16 |
17 | export const DatePickerPreview: React.FunctionComponent = (props) => {
18 | const onDeleteComponent = (keyVal: number) => {
19 | props.onDeleteComponent(keyVal);
20 | }
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | onDeleteComponent(props.keyVal)} />
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "incidentreporter",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fluentui/react": "^0.43.2",
7 | "@microsoft/applicationinsights-react-js": "^2.3.1",
8 | "@microsoft/teams-js": "^1.5.2",
9 | "axios": "^0.21.1",
10 | "classnames": "^2.2.6",
11 | "react": "^16.10.2",
12 | "react-adal": "^0.5.0",
13 | "react-appinsights": "^3.0.0-rc.6",
14 | "react-dom": "^16.10.2",
15 | "react-router": "^5.1.2",
16 | "react-router-dom": "^5.1.2",
17 | "react-scripts": "^4.0.3",
18 | "typescript": "^3.6.4"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": "react-app"
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | },
41 | "devDependencies": {
42 | "@types/react": "16.9.6",
43 | "@types/react-dom": "16.9.2",
44 | "@types/react-router-dom": "^5.1.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Manifest/SME/he.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "תמיכה מרוחקת – מומחה",
4 | "description.short": "בוט של צוות מומחים לטיפול בבקשות המשתמשים ולהגדרת מומחים זמינים.",
5 | "description.full": "נהל בקשת תמיכה והגדר מומחים זמינים בצוות. חפש בקשות דחופות, מוקצות ולא מוקצות, ישירות מצוות המומחים.",
6 | "bots[0].commandLists[0].commands[0].title": "רשימת מומחים",
7 | "bots[0].commandLists[0].commands[0].description": "רשימת מומחים זמינים",
8 | "composeExtensions[0].commands[0].title": "דחוף",
9 | "composeExtensions[0].commands[0].description": "חפש בקשות דחופות",
10 | "composeExtensions[0].commands[0].parameters[0].title": "חפש",
11 | "composeExtensions[0].commands[0].parameters[0].description": "חיפוש בקשות",
12 | "composeExtensions[0].commands[1].title": "הוקצה",
13 | "composeExtensions[0].commands[1].description": "חפש בקשות שהוקצו למומחה",
14 | "composeExtensions[0].commands[1].parameters[0].title": "חפש",
15 | "composeExtensions[0].commands[1].parameters[0].description": "חיפוש בקשות",
16 | "composeExtensions[0].commands[2].title": "לא הוקצה",
17 | "composeExtensions[0].commands[2].description": "חפש בקשות שלא הוקצו עדיין לא הוקצו למומחים",
18 | "composeExtensions[0].commands[2].parameters[0].title": "חפש",
19 | "composeExtensions[0].commands[2].parameters[0].description": "חיפוש בקשות"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/ITicketDetailStorageProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Threading.Tasks;
8 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
9 |
10 | ///
11 | /// Ticket provider helps in fetching and storing information in storage table.
12 | ///
13 | public interface ITicketDetailStorageProvider
14 | {
15 | ///
16 | /// Save or update ticket entity.
17 | ///
18 | /// Ticket received from bot based on which appropriate row will replaced or inserted in table storage.
19 | /// that resolves successfully if the data was saved successfully.
20 | Task UpsertTicketAsync(TicketDetail ticketDetails);
21 |
22 | ///
23 | /// Get already saved entity detail from storage table.
24 | ///
25 | /// ticket id received from bot based on which appropriate row data will be fetched.
26 | /// Already saved entity detail.
27 | Task GetTicketAsync(string ticketId);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/ITicketSearchService.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Collections.Generic;
8 | using System.Threading.Tasks;
9 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
10 |
11 | ///
12 | /// Interface of search service which will help in creating index, indexer and data source if it doesn't exist.
13 | ///
14 | public interface ITicketSearchService
15 | {
16 | ///
17 | /// Provide search result for table to be used by SME based on Azure search service.
18 | ///
19 | /// Scope of the search.
20 | /// searchQuery to be provided by message extension.
21 | /// Number of search results to return.
22 | /// Number of search results to skip.
23 | /// Requester id of the user to get specific tickets.
24 | /// List of search results.
25 | Task> SearchTicketsAsync(TicketSearchScope searchScope, string searchQuery, int? count = null, int? skip = null, string requestorId = "");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "MicrosoftAppId": "",
3 | "MicrosoftAppPassword": "",
4 | "Logging": {
5 | "LogLevel": {
6 | "Default": "Information"
7 | }
8 | },
9 | "ApplicationInsights": {
10 | "InstrumentationKey": "",
11 | "LogLevel": {
12 | "Default": "Information",
13 | "Microsoft": "Information"
14 | }
15 | },
16 | "AllowedHosts": "*",
17 | "Bot": {
18 | "AppBaseUri": "",
19 | "TenantId": "",
20 | "TeamLink": ""
21 | },
22 | "Card": {
23 | "DefaultCardTemplate": "[{\"type\": \"Input.Date\",\"id\": \"First observed on_IssueOccuredOn\",\"max\":\"_maxDate_\",\"value\": \"_issueDate_\"},{\"type\": \"TextBlock\",\"id\": \"Validation Message_ValidationMessage\",\"text\": \"_dateValidationText_\",\"spacing\": \"None\",\"color\": \"Attention\",\"isVisible\": \"_showDateValidation_\"}]"
24 | },
25 | "Storage": {
26 | "ConnectionString": ""
27 | },
28 | "UppercaseResponse": false,
29 | "i18n": {
30 | "DefaultCulture": "en",
31 | "SupportedCultures": "en,ar,de,es,fr,he,ja,ko,pt-BR,ru,zh-CN,zh-TW"
32 | },
33 | "Search": {
34 | "SearchServiceName": "",
35 | "SearchServiceAdminApiKey": "",
36 | "SearchServiceQueryApiKey": "",
37 | "SearchIndexingIntervalInMinutes": 10
38 | },
39 | "Token": {
40 | "SecurityKey": ""
41 | }
42 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/IOnCallSupportDetailStorageProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Threading.Tasks;
8 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
9 |
10 | ///
11 | /// Interface for on call support detail provider.
12 | ///
13 | public interface IOnCallSupportDetailStorageProvider
14 | {
15 | ///
16 | /// Save on call support details in Azure Table Storage.
17 | ///
18 | /// On call support details to be stored in table storage.
19 | /// Returns OnCallSupportId when on call support data was saved successfully.
20 | Task UpsertOnCallSupportDetailsAsync(OnCallSupportDetail onCallSupportTeamDetails);
21 |
22 | ///
23 | /// Get already saved entity detail from storage table.
24 | ///
25 | /// onCallSMEId received from bot based on which appropriate row data will be fetched.
26 | /// Already saved entity detail.
27 | Task GetOnCallSupportDetailAsync(string onCallSMEId);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/checkbox-preview.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React from "react";
8 | import { Flex, Icon, Checkbox, Text } from '@fluentui/react';
9 | import "../styles/theme.css";
10 |
11 | interface IPreviewProps {
12 | keyVal: number,
13 | displayName: string,
14 | options: Array,
15 | onDeleteComponent: (keyVal: number) => void
16 | }
17 |
18 | export const CheckBoxPreview: React.FunctionComponent = (props) => {
19 | const onDeleteComponent = (keyVal: number) => {
20 | props.onDeleteComponent(keyVal);
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 | {props.options.map(function(option, index){
30 | return <>
>
31 | })}
32 |
33 |
34 |
35 | onDeleteComponent(props.keyVal)} />
36 |
37 | );
38 | }
--------------------------------------------------------------------------------
/Manifest/SME/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "دعم عن بعد - خبير",
4 | "description.short": "روبوت فريق الخبراء يعالج طلبات المستخدمين ويعين خبراء متوفرين عند الطلب.",
5 | "description.full": "يمكنك إدارة طلب الدعم وتعيين الخبراء المتوفرين عند الطلب في الفريق كما يمكنك البحث في الطلبات العاجلة والمعينة وغير المعينة مباشرةً من فريق الخبراء.",
6 | "bots[0].commandLists[0].commands[0].title": "قائمة الخبراء",
7 | "bots[0].commandLists[0].commands[0].description": "قائمة الخبراء المتوفرين عند الطلب",
8 | "composeExtensions[0].commands[0].title": "العاجلة",
9 | "composeExtensions[0].commands[0].description": "بحث في الطلبات العاجلة",
10 | "composeExtensions[0].commands[0].parameters[0].title": "بحث",
11 | "composeExtensions[0].commands[0].parameters[0].description": "بحث في الطلبات",
12 | "composeExtensions[0].commands[1].title": "المعينة",
13 | "composeExtensions[0].commands[1].description": "بحث في الطلبات المعينة إلى خبير",
14 | "composeExtensions[0].commands[1].parameters[0].title": "بحث",
15 | "composeExtensions[0].commands[1].parameters[0].description": "بحث في الطلبات",
16 | "composeExtensions[0].commands[2].title": "غير المعينة",
17 | "composeExtensions[0].commands[2].description": "بحث في الطلبات غير المعينة بعد إلى خبير",
18 | "composeExtensions[0].commands[2].parameters[0].title": "بحث",
19 | "composeExtensions[0].commands[2].parameters[0].description": "بحث في الطلبات"
20 | }
--------------------------------------------------------------------------------
/Manifest/SME/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Remote Support - Expert",
4 | "description.short": "Expert team bot to handle user requests and set on call experts.",
5 | "description.full": "Manage support request and set up on call experts in team. Search urgent, assigned and unassigned requests right from expert team.",
6 | "bots[0].commandLists[0].commands[0].title": "Expert list",
7 | "bots[0].commandLists[0].commands[0].description": "List of on call experts",
8 | "composeExtensions[0].commands[0].title": "Urgent",
9 | "composeExtensions[0].commands[0].description": "Search urgent requests",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Search",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Search requests",
12 | "composeExtensions[0].commands[1].title": "Assigned",
13 | "composeExtensions[0].commands[1].description": "Search requests assigned to an expert",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Search",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Search requests",
16 | "composeExtensions[0].commands[2].title": "Unassigned",
17 | "composeExtensions[0].commands[2].description": "Search unassigned requests not yet assigned to an expert",
18 | "composeExtensions[0].commands[2].parameters[0].title": "Search",
19 | "composeExtensions[0].commands[2].parameters[0].description": "Search requests"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/OnCallExpertsDetail.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using System.Collections.Generic;
8 | using Newtonsoft.Json;
9 |
10 | ///
11 | /// Class contains details of the on call experts.
12 | ///
13 | public class OnCallExpertsDetail
14 | {
15 | ///
16 | /// Gets or sets list of on call experts.
17 | ///
18 | [JsonProperty("oncallexpertslist")]
19 | #pragma warning disable CA2227 // Collection properties should be read only - Need to set this property from json response from client Application.
20 | public List OnCallExperts { get; set; }
21 | #pragma warning restore CA2227 // Collection properties should be read only
22 |
23 | ///
24 | /// Gets or sets unique identifier of the on call support created.
25 | ///
26 | [JsonProperty("oncallsupportid")]
27 | public string OnCallSupportId { get; set; }
28 |
29 | ///
30 | /// Gets or sets card activity id which need to be refreshed in channel with updated details.
31 | ///
32 | [JsonProperty("oncallsupportcardactivityid")]
33 | public string OnCallSupportCardActivityId { get; set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/Program.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Configuration
6 | {
7 | using Microsoft.AspNetCore;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.Extensions.Logging;
10 |
11 | ///
12 | /// Program main class.
13 | ///
14 | public class Program
15 | {
16 | ///
17 | /// Main method.
18 | ///
19 | /// string of arguments.
20 | public static void Main(string[] args)
21 | {
22 | CreateWebHostBuilder(args).Build().Run();
23 | }
24 |
25 | ///
26 | /// This method sets up configurations of the application.
27 | ///
28 | /// string of arguments.
29 | /// unit of execution.
30 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
31 | WebHost.CreateDefaultBuilder(args)
32 | .ConfigureLogging((hostContext, logging) =>
33 | {
34 | var appInsightKey = hostContext.Configuration.GetSection("ApplicationInsights")["InstrumentationKey"];
35 | logging.AddApplicationInsights(appInsightKey);
36 | })
37 | .UseStartup();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Utility/Utility.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common
6 | {
7 | using System;
8 | using System.Text.RegularExpressions;
9 | using System.Web;
10 |
11 | ///
12 | /// Utility class for common functionality.
13 | ///
14 | public static class Utility
15 | {
16 | ///
17 | /// Based on deep link URL received find team id and set it.
18 | ///
19 | /// Deep link to get the team id.
20 | /// A team id from the deep link URL.
21 | public static string ParseTeamIdFromDeepLink(string teamIdDeepLink)
22 | {
23 | // team id regex match
24 | // for a pattern like https://teams.microsoft.com/l/team/19%3a64c719819fb1412db8a28fd4a30b581a%40thread.tacv2/conversations?groupId=53b4782c-7c98-4449-993a-441870d10af9&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47
25 | // regex checks for 19%3a64c719819fb1412db8a28fd4a30b581a%40thread.tacv2
26 | var match = Regex.Match(teamIdDeepLink, @"teams.microsoft.com/l/team/(\S+)/");
27 | if (!match.Success)
28 | {
29 | throw new ArgumentException($"Invalid team found.");
30 | }
31 |
32 | return HttpUtility.UrlDecode(match.Groups[1].Value);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Manifest/SME/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Удаленная поддержка — эксперт",
4 | "description.short": "Бот команды экспертов для обработки запросов и подготовки дежурных экспертов.",
5 | "description.full": "Управляйте запросом в службу поддержки и подготовьте дежурных экспертов в команде. Находите срочные, назначенные и неназначенные запросы непосредственно с помощью команды экспертов.",
6 | "bots[0].commandLists[0].commands[0].title": "Список экспертов",
7 | "bots[0].commandLists[0].commands[0].description": "Список дежурных экспертов",
8 | "composeExtensions[0].commands[0].title": "Срочно",
9 | "composeExtensions[0].commands[0].description": "Поиск срочных запросов",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Поиск",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Поиск запросов",
12 | "composeExtensions[0].commands[1].title": "Назначено",
13 | "composeExtensions[0].commands[1].description": "Поиск запросов, назначенных эксперту",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Поиск",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Поиск запросов",
16 | "composeExtensions[0].commands[2].title": "Не назначено",
17 | "composeExtensions[0].commands[2].description": "Поиск запросов, не назначенных эксперту",
18 | "composeExtensions[0].commands[2].parameters[0].title": "Поиск",
19 | "composeExtensions[0].commands[2].parameters[0].description": "Поиск запросов"
20 | }
--------------------------------------------------------------------------------
/Manifest/SME/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Soporte remoto: experto",
4 | "description.short": "Bot experto para manejar peticiones usuarios y configurar expertos llamadas.",
5 | "description.full": "Administre la solicitud de soporte técnico y configure a expertos de llamada en equipo. Busque solicitudes urgentes, asignadas y sin asignar directamente desde un equipo experto.",
6 | "bots[0].commandLists[0].commands[0].title": "Lista de expertos",
7 | "bots[0].commandLists[0].commands[0].description": "Lista de expertos en llamada",
8 | "composeExtensions[0].commands[0].title": "Urgente",
9 | "composeExtensions[0].commands[0].description": "Buscar solicitudes urgentes",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Buscar",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Solicitudes de búsqueda",
12 | "composeExtensions[0].commands[1].title": "Asignado",
13 | "composeExtensions[0].commands[1].description": "Buscar solicitudes asignadas a un experto",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Buscar",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Solicitudes de búsqueda",
16 | "composeExtensions[0].commands[2].title": "Sin asignar",
17 | "composeExtensions[0].commands[2].description": "Buscar solicitudes sin asignar aún no asignadas a un experto",
18 | "composeExtensions[0].commands[2].parameters[0].title": "Buscar",
19 | "composeExtensions[0].commands[2].parameters[0].description": "Solicitudes de búsqueda"
20 | }
--------------------------------------------------------------------------------
/Manifest/SME/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Remotesupport – Experte",
4 | "description.short": "Expertenteam-Bot für Benutzeranfragen und zum Setzen von Experten auf Abruf.",
5 | "description.full": "Verwalten Sie Supportanfragen und legen Sie Rufbereitschaftsexperten im Team fest. Suchen Sie dringende, zugewiesene und nicht zugewiesene Anfragen direkt vom Expertenteam.",
6 | "bots[0].commandLists[0].commands[0].title": "Experten-Liste",
7 | "bots[0].commandLists[0].commands[0].description": "Liste der Rufbereitschaftsexperten",
8 | "composeExtensions[0].commands[0].title": "Dringend",
9 | "composeExtensions[0].commands[0].description": "Dringende Anforderungen durchsuchen",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Suchen",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Suchanforderungen",
12 | "composeExtensions[0].commands[1].title": "Zugewiesen",
13 | "composeExtensions[0].commands[1].description": "Einem Experten zugewiesene Suchanforderungen",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Suchen",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Suchanforderungen",
16 | "composeExtensions[0].commands[2].title": "Nicht zugeordnet",
17 | "composeExtensions[0].commands[2].description": "Nicht zugewiesene Suchanforderungen suchen, die noch nicht einem Experten zugewiesen sind",
18 | "composeExtensions[0].commands[2].parameters[0].title": "Suchen",
19 | "composeExtensions[0].commands[2].parameters[0].description": "Suchanforderungen"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Models/InputChoiceSet.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Models
6 | {
7 | using System.Collections.Generic;
8 | using AdaptiveCards;
9 | using Newtonsoft.Json;
10 |
11 | ///
12 | /// InputChoiceSet
13 | ///
14 | public class InputChoiceSet
15 | {
16 | ///
17 | /// Gets or Sets type
18 | ///
19 | [JsonProperty("type")]
20 | public string Type { get; set; }
21 |
22 | ///
23 | /// Gets Choices.
24 | ///
25 | [JsonProperty("choices")]
26 | public List Choices { get; } = new List();
27 |
28 | ///
29 | /// Gets or sets a value indicating whether gets or Sets indicating whether gets or sets check box is enabled or not.
30 | ///
31 | [JsonProperty("isMultiSelect")]
32 | public bool IsMultiSelect { get; set; }
33 |
34 | ///
35 | /// Gets or Sets Input id.
36 | ///
37 | [JsonProperty("id")]
38 | public string Id { get; set; }
39 |
40 | ///
41 | /// Gets or sets style.
42 | ///
43 | public AdaptiveChoiceInputStyle Style { get; set; }
44 |
45 | ///
46 | /// Gets or sets input elemenet value.
47 | ///
48 | public string Value { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Manifest/SME/pt-BR.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Suporte Remoto - Especialista",
4 | "description.short": "Bot da equipe de esp. para lidar com solicitações e definir os esp. em chamada.",
5 | "description.full": "Gerencie a solicitação de suporte e configure os especialistas em chamada na equipe. Pesquise solicitações urgentes, atribuídas e não atribuídas diretamente da equipe de especialistas.",
6 | "bots[0].commandLists[0].commands[0].title": "Lista de especialistas",
7 | "bots[0].commandLists[0].commands[0].description": "Lista de especialistas em chamada",
8 | "composeExtensions[0].commands[0].title": "Urgente",
9 | "composeExtensions[0].commands[0].description": "Pesquisar pedidos urgentes",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Pesquisar",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Pedidos de pesquisa",
12 | "composeExtensions[0].commands[1].title": "Atribuída",
13 | "composeExtensions[0].commands[1].description": "Solicitações de pesquisa atribuídas a um especialista",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Pesquisar",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Pedidos de pesquisa",
16 | "composeExtensions[0].commands[2].title": "Não atribuída",
17 | "composeExtensions[0].commands[2].description": "Pesquisa solicitações não atribuídas que ainda não foram atribuídas a um especialista",
18 | "composeExtensions[0].commands[2].parameters[0].title": "Pesquisar",
19 | "composeExtensions[0].commands[2].parameters[0].description": "Pedidos de pesquisa"
20 | }
--------------------------------------------------------------------------------
/Manifest/SME/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "name.short": "Support à distance – expert",
4 | "description.short": "Bot d’experts traitant les requêtes d’utilisateurs et les experts de garde.",
5 | "description.full": "Gérez la demande de support et établissez les experts disponibles dans l’équipe. Recherchez parmi les demandes urgentes, affectées et non affectées directement à partir de l’équipe d’experts.",
6 | "bots[0].commandLists[0].commands[0].title": "Liste d’experts",
7 | "bots[0].commandLists[0].commands[0].description": "Liste des experts disponibles",
8 | "composeExtensions[0].commands[0].title": "Urgentes",
9 | "composeExtensions[0].commands[0].description": "Recherchez parmi les demandes urgentes",
10 | "composeExtensions[0].commands[0].parameters[0].title": "Rechercher",
11 | "composeExtensions[0].commands[0].parameters[0].description": "Recherchez parmi les demandes",
12 | "composeExtensions[0].commands[1].title": "Affectées",
13 | "composeExtensions[0].commands[1].description": "Recherchez parmi les demandes affectées à un expert",
14 | "composeExtensions[0].commands[1].parameters[0].title": "Rechercher",
15 | "composeExtensions[0].commands[1].parameters[0].description": "Recherchez parmi les demandes",
16 | "composeExtensions[0].commands[2].title": "Non affectées",
17 | "composeExtensions[0].commands[2].description": "Recherchez parmi les demandes qui ne sont pas encore affectées à un expert",
18 | "composeExtensions[0].commands[2].parameters[0].title": "Rechercher",
19 | "composeExtensions[0].commands[2].parameters[0].description": "Recherchez parmi les demandes"
20 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/styles/theme.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | .container-div {
8 | height: 100vh;
9 | padding-left: 2.5rem;
10 | padding-right: 2.5rem;
11 | width: auto;
12 | overflow: auto;
13 | }
14 |
15 | .dialog-container-div {
16 | height: 60vh;
17 | padding-left: 2.5rem;
18 | padding-right: 2.5rem;
19 | width: auto;
20 | overflow: auto;
21 | }
22 |
23 | .container-subdiv {
24 | height: 82vh;
25 | overflow-y:auto;
26 | width: auto;
27 | }
28 |
29 | .menu {
30 | border-bottom: 0px;
31 | margin-bottom: 1rem;
32 | margin-top: 0.5rem
33 | }
34 |
35 | .footer {
36 | position: fixed;
37 | bottom: 0;
38 | right: 0;
39 | width: inherit;
40 | padding-bottom: 1.5rem;
41 | padding-left: 2.5rem;
42 | padding-right: 2.5rem;
43 | }
44 |
45 | .div-shadow {
46 | border: 1px solid #E2E2E2;
47 | box-shadow: 0 0 10px 0 rgba(0,0,0,0.15);
48 | border-radius: 3px;
49 | border-radius: 3px;
50 | }
51 |
52 | .add-icon {
53 | padding: 1rem;
54 | }
55 |
56 | .add-icon:hover {
57 | cursor: pointer;
58 | }
59 |
60 | .preview-item{
61 | padding: .5rem;
62 | }
63 |
64 | .preview-item:hover {
65 | cursor: pointer;
66 | background-color: #8B8CC7;
67 | }
68 | .medium-margin-top{
69 | margin-top: 1rem;
70 | }
71 | .small-margin-right {
72 | margin-right: 0.5rem;
73 | }
74 | .common-padding{
75 | padding: 1rem;
76 | }
77 |
78 | .team-link {
79 | margin-left: -18rem;
80 | margin-top: 0.3rem;
81 | }
82 |
83 | .team-textbox-width {
84 | width: 30rem;
85 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/datepicker-form.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React, { useState } from "react";
8 | import { Button, Icon, Text, Flex, Input } from '@fluentui/react';
9 | import "../styles/theme.css";
10 |
11 | interface IPropertiesProps {
12 | onAddComponent: (properties: any) => boolean,
13 | resourceStrings: any,
14 | }
15 |
16 |
17 | const DatePickerForm: React.FunctionComponent = (props) => {
18 | const [properties, setProperties] = useState({ type: 'Input.Date', displayName: '' });
19 | const onAddComponent = (event: any) => {
20 | let result = props.onAddComponent(properties);
21 | if (result) {
22 | setProperties({ type: 'Input.Date', displayName: '' });
23 | }
24 | }
25 |
26 | return (
27 | <>
28 |
29 | <>
30 |
31 | { setProperties({ ...properties, displayName: e.target.value }) }} />
32 |
33 |
34 |
35 |
36 | >
37 |
38 |
39 | >
40 | );
41 | }
42 |
43 | export default DatePickerForm;
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/components/input-text-form.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import React, { useState } from "react";
8 | import { Button, Input, Text, Flex } from '@fluentui/react';
9 |
10 | interface IPropertiesProps {
11 | onAddComponent: (properties: any) => boolean,
12 | resourceStrings: any,
13 | }
14 |
15 | const InputTextForm: React.FunctionComponent = (props) => {
16 | const [properties, setProperties] = useState({ type: 'Input.Text', placeholder: '', displayName: '' });
17 | const onAddComponent = (event: any) => {
18 | let result = props.onAddComponent(properties);
19 | if (result) {
20 | setProperties({ type: 'Input.Text', placeholder: '', displayName: '' });
21 | }
22 | }
23 |
24 | return (
25 | <>
26 |
27 | <>
28 |
29 | { setProperties({ ...properties, displayName: e.target.value }) }} />
30 |
31 | { setProperties({ ...properties, placeholder: e.target.value }) }} />
32 | >
33 |
34 |
35 | >
36 | );
37 | }
38 |
39 | export default InputTextForm;
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/ChangeTicketStatus.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using Newtonsoft.Json;
8 |
9 | ///
10 | /// Represents the data payload of Action.Submit to change the status of a ticket.
11 | ///
12 | public class ChangeTicketStatus
13 | {
14 | ///
15 | /// Action that reopens a closed ticket.
16 | ///
17 | public const string ReopenAction = "Reopen";
18 |
19 | ///
20 | /// Action that closes a ticket.
21 | ///
22 | public const string CloseAction = "Close";
23 |
24 | ///
25 | /// Action that relates to change in severity of an ticket.
26 | ///
27 | public const string RequestTypeAction = "RequestType";
28 |
29 | ///
30 | /// Action that assigns a ticket to the person that performed the action.
31 | ///
32 | public const string AssignToSelfAction = "AssignToSelf";
33 |
34 | ///
35 | /// Gets or sets the ticket id.
36 | ///
37 | [JsonProperty("ticketId")]
38 | public string TicketId { get; set; }
39 |
40 | ///
41 | /// Gets or sets the action to perform on the ticket.
42 | ///
43 | [JsonProperty("action")]
44 | public string Action { get; set; }
45 |
46 | ///
47 | /// Gets or sets the severity of the ticketId.
48 | ///
49 | [JsonProperty("RequestType")]
50 | public string RequestType { get; set; }
51 | }
52 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Models/AdaptiveCardAction.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Models
6 | {
7 | using Microsoft.Bot.Schema;
8 | using Newtonsoft.Json;
9 |
10 | ///
11 | /// Adaptive card action model class.
12 | ///
13 | public class AdaptiveCardAction
14 | {
15 | ///
16 | /// Gets or sets Ms Teams card action type.
17 | ///
18 | [JsonProperty("msteams")]
19 | public CardAction MsteamsCardAction { get; set; }
20 |
21 | ///
22 | /// Gets or sets commands from which task module is invoked.
23 | ///
24 | [JsonProperty("command")]
25 | public string Command { get; set; }
26 |
27 | ///
28 | /// Gets or sets TicketId from TicketDetail.
29 | ///
30 | [JsonProperty("postedValues")]
31 | public string PostedValues { get; set; }
32 |
33 | ///
34 | /// Gets or sets card id.
35 | ///
36 | [JsonProperty("cardId")]
37 | public string CardId { get; set; }
38 |
39 | ///
40 | /// Gets or sets card id.
41 | ///
42 | [JsonProperty("teamId")]
43 | public string TeamId { get; set; }
44 |
45 | ///
46 | /// Gets or sets ticket id.
47 | ///
48 | [JsonProperty("ticketId")]
49 | public string TicketId { get; set; }
50 |
51 | ///
52 | /// Gets or sets the activity associated with this turn.
53 | ///
54 | [JsonProperty("activityId")]
55 | public string ActivityId { get; set; }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/api/axios-decorator.ts:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | import axios, { AxiosResponse, AxiosRequestConfig } from "axios";
6 |
7 | export class AxiosJWTDecorator {
8 |
9 | /**
10 | * Post data to API
11 | * @param {String} url Resource URI
12 | * @param {Object} data Request body data
13 | * @param {String} token Custom JWT token
14 | */
15 | public async post>(
16 | url: string,
17 | data?: any,
18 | token?: string
19 | ): Promise {
20 | try {
21 | let config: AxiosRequestConfig = axios.defaults;
22 | config.headers["Authorization"] = `Bearer ${token}`;
23 |
24 | return await axios.post(url, data, config);
25 | } catch (error) {
26 | return error.response;
27 | }
28 | }
29 |
30 | /**
31 | * Post data to API
32 | * @param {String} url Resource URI
33 | * @param {Object} data Request body data
34 | * @param {String} token Custom JWT token
35 | */
36 | public async Put>(
37 | url: string,
38 | data?: any,
39 | token?: string
40 | ): Promise {
41 | try {
42 | let config: AxiosRequestConfig = axios.defaults;
43 | config.headers["Authorization"] = `Bearer ${token}`;
44 |
45 | return await axios.put(url, data, config);
46 | } catch (error) {
47 | return error.response;
48 | }
49 | }
50 |
51 | /**
52 | * Get data to API
53 | * @param {String} token Custom JWT token
54 | */
55 | public async get>(
56 | url: string,
57 | token?: string
58 | ): Promise {
59 | try {
60 | let config: AxiosRequestConfig = axios.defaults;
61 | config.headers["Authorization"] = `Bearer ${token}`;
62 | return await axios.get(url, config);
63 | } catch (error) {
64 | return error.response;
65 | }
66 | }
67 | }
68 |
69 | const axiosJWTDecoratorInstance = new AxiosJWTDecorator();
70 | export default axiosJWTDecoratorInstance;
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/Controllers/SettingsController.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Configuration
6 | {
7 | using System;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Options;
11 | using Microsoft.Teams.Apps.RemoteSupport.Configuration.Models;
12 |
13 | ///
14 | /// This endpoint is used to provide app settings to client app.
15 | ///
16 | [Route("api/[controller]")]
17 | [ApiController]
18 | public class SettingsController : ControllerBase
19 | {
20 | private readonly AzureAdSettings azureAdSettings;
21 | private readonly ILogger logger;
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Azure ad configurations.
27 | /// Logger instance.
28 | public SettingsController(IOptionsMonitor options, ILogger logger)
29 | {
30 | this.azureAdSettings = options?.CurrentValue;
31 | this.logger = logger;
32 | }
33 |
34 | ///
35 | /// This endpoint gives Azure AD configurations.
36 | ///
37 | /// object.
38 | [HttpGet]
39 | public ActionResult GetSettings()
40 | {
41 | try
42 | {
43 | return this.Ok(new
44 | {
45 | ClientId = this.azureAdSettings.ClientId,
46 | TenantId = this.azureAdSettings.Tenant,
47 | TokenEndpoint = this.azureAdSettings.Instance + this.azureAdSettings.Tenant,
48 | });
49 | }
50 | catch (Exception ex)
51 | {
52 | this.logger.LogError(ex, "Error in fetching app settings");
53 | throw;
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Controllers/BotController.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport
6 | {
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.Bot.Builder;
11 | using Microsoft.Bot.Builder.Integration.AspNet.Core;
12 |
13 | ///
14 | /// The BotController is responsible for connecting the Asp.Net MVC pipeline to the
15 | /// and the underlying activity handler ().
16 | ///
17 | [Route("api/messages")]
18 | [ApiController]
19 | public class BotController : ControllerBase
20 | {
21 | private readonly IBotFrameworkHttpAdapter botFrameworkHttpAdapter;
22 | private readonly IBot activityHandler;
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | /// The BotFramework adapter.
28 | /// The underlying activity handler.
29 | public BotController(IBotFrameworkHttpAdapter adapter, IBot activityHandler)
30 | {
31 | this.botFrameworkHttpAdapter = adapter;
32 | this.activityHandler = activityHandler;
33 | }
34 |
35 | ///
36 | /// Handle inbound messages from the BotFramework service.
37 | ///
38 | /// The cancellation token for the underlying request.
39 | /// A Task that resolves when the message is processed.
40 | [HttpPost]
41 | public async Task PostAsync(CancellationToken cancellationToken)
42 | {
43 | await this.botFrameworkHttpAdapter
44 | .ProcessAsync(
45 | httpRequest: this.Request,
46 | httpResponse: this.Response,
47 | bot: this.activityHandler,
48 | cancellationToken: cancellationToken);
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/ICardConfigurationStorageProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Collections.Generic;
8 | using System.Threading.Tasks;
9 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
10 |
11 | ///
12 | /// Card configuration helps in fetching and storing dynamic card configuration in Azure table storage.
13 | ///
14 | public interface ICardConfigurationStorageProvider
15 | {
16 | ///
17 | /// Stores configuration details into Azure table storage.
18 | ///
19 | /// Configuration storage entity.
20 | /// A task that represents configuration entity is saved or updated.
21 | Task StoreOrUpdateEntityAsync(CardConfigurationEntity configurationEntity);
22 |
23 | ///
24 | /// Returns the latest card template present in the Azure table storage.
25 | ///
26 | /// configuration details.
27 | Task GetConfigurationAsync();
28 |
29 | ///
30 | /// Returns the latest card template present in the Azure table storage by CardId
31 | ///
32 | /// Unique identifier of the card configuration.
33 | /// A configuration details.
34 | Task GetConfigurationsByCardIdAsync(string cardId);
35 |
36 | ///
37 | /// Returns the Adaptive card item element {Id, display name} mapping present in the Azure table storage by CardId
38 | ///
39 | /// Unique identifier of the card configuration.
40 | /// A configuration details.
41 | Task> GetCardItemElementMappingAsync(string cardId);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/ClientApp/src/api/incident-api.ts:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | import axios from "./axios-decorator";
6 |
7 | const baseAxiosUrl = window.location.origin;
8 |
9 | /**
10 | * Get localized resource strings from API
11 | */
12 | export const getResourceStrings = async (token: string): Promise => {
13 |
14 | let url = baseAxiosUrl + "/api/resource/resourcestrings";
15 | let resourceStringsResponse = await axios.get(url, token);
16 | return resourceStringsResponse;
17 | }
18 |
19 | /**
20 | * Get card configuration saved in storage.
21 | * @param {String | Null} token Custom JWT token.
22 | */
23 | export const getConfigurationsAsync = async (token: string): Promise => {
24 |
25 | let url = baseAxiosUrl + "/api/Storage";
26 | let configurationsResponse = await axios.get(url, token);
27 | return configurationsResponse;
28 | }
29 |
30 | /**
31 | * Get Azure Active Directory settings for authentication.
32 | */
33 | export const getAzureActiveDirectorySettingsAsync = async (): Promise => {
34 |
35 | let url = baseAxiosUrl + "/api/Settings";
36 | let settingsResponse = await axios.get(url, "");
37 | if (settingsResponse.status === 200) {
38 | localStorage.setItem("TenantId", settingsResponse.data.TenantId);
39 | localStorage.setItem("ClientId", settingsResponse.data.ClientId);
40 | localStorage.setItem("TokenEndpoint", settingsResponse.data.TokenEndpoint);
41 | }
42 | return true;
43 | }
44 |
45 | /**
46 | * Save card configuration details in Azure storage.
47 | * @param {configurationDetails | Null} configurationDetails configuration details.
48 | * @param {String | Null} token Custom JWT token.
49 | */
50 | export const saveConfigurationsAsync = async (configurationDetails: any, token: string): Promise => {
51 |
52 | let url = baseAxiosUrl + "/api/Storage";
53 | let saveConfigurationsResult = await axios.post(url, configurationDetails, token);
54 | return saveConfigurationsResult;
55 | }
56 |
57 | export const getAzureActiveDirectorySettings = async () => {
58 | await getAzureActiveDirectorySettingsAsync();
59 | };
60 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Microsoft.Teams.Apps.RemoteSupport.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 | Always
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 | all
22 |
23 |
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 | all
26 |
27 |
28 | runtime; build; native; contentfiles; analyzers; buildtransitive
29 | all
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), .gitignore))\Build\Analyzer.ruleset
41 | bin\$(Configuration)\$(Platform)\$(AssemblyName).xml
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/CardConfigurationEntity.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using System;
8 | using Microsoft.WindowsAzure.Storage.Table;
9 | using Newtonsoft.Json;
10 | using Newtonsoft.Json.Serialization;
11 |
12 | ///
13 | /// Class contains details of card configuration created in table storage.
14 | ///
15 | public class CardConfigurationEntity : TableEntity
16 | {
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | public CardConfigurationEntity()
21 | {
22 | this.PartitionKey = Constants.CardConfigurationPartitionKey;
23 | }
24 |
25 | ///
26 | /// Gets or sets GUID to uniquely identifies the Card.
27 | ///
28 | public string CardId
29 | {
30 | get { return this.RowKey; }
31 | set { this.RowKey = value; }
32 | }
33 |
34 | ///
35 | /// Gets or sets Id of the team in which card configuration created.
36 | ///
37 | [JsonProperty("TeamId")]
38 | public string TeamId { get; set; }
39 |
40 | ///
41 | /// Gets or sets url of the expert team.
42 | ///
43 | [JsonProperty("TeamLink")]
44 | public string TeamLink { get; set; }
45 |
46 | ///
47 | /// Gets or sets card creation time.
48 | ///
49 | public DateTime CreatedOn { get; set; }
50 |
51 | ///
52 | /// Gets or sets the user principal name (UPN) of the user that created the ticket.
53 | ///
54 | public string CreatedByUserPrincipalName { get; set; }
55 |
56 | ///
57 | /// Gets or sets Azure Active Directory objectId of user who created ticket.
58 | ///
59 | public string CreatedByObjectId { get; set; }
60 |
61 | ///
62 | /// Gets or sets adaptive card items json properties.
63 | ///
64 | [JsonProperty("CardTemplate")]
65 | public string CardTemplate { get; set; }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/api/axios-decorator.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import axios, { AxiosResponse, AxiosRequestConfig } from "axios";
8 |
9 | export class AxiosJWTDecorator {
10 |
11 | /**
12 | * Post data to API
13 | * @param {String} url Resource URI
14 | * @param {Object} data Request body data
15 | * @param {String} token Custom JWT token
16 | */
17 | public async post>(
18 | url: string,
19 | data?: any,
20 | token?: string
21 | ): Promise {
22 | try {
23 | let config: AxiosRequestConfig = axios.defaults;
24 | config.headers["Authorization"] = `Bearer ${token}`;
25 |
26 | return await axios.post(url, data, config);
27 | } catch (error) {
28 | return error.response;
29 | }
30 | }
31 |
32 | /**
33 | * Post data to API
34 | * @param {String} url Resource URI
35 | * @param {Object} data Request body data
36 | * @param {String} token Custom JWT token
37 | */
38 | public async Put>(
39 | url: string,
40 | data?: any,
41 | token?: string
42 | ): Promise {
43 | try {
44 | let config: AxiosRequestConfig = axios.defaults;
45 | config.headers["Authorization"] = `Bearer ${token}`;
46 |
47 | return await axios.put(url, data, config);
48 | } catch (error) {
49 | return error.response;
50 | }
51 | }
52 |
53 | /**
54 | * Get data to API
55 | * @param {String} token Custom JWT token
56 | */
57 | public async get>(
58 | url: string,
59 | token?: string,
60 | locale?: string | null
61 | ): Promise {
62 | try {
63 | let config: AxiosRequestConfig = axios.defaults;
64 | config.headers["Authorization"] = `Bearer ${token}`;
65 | if (locale) {
66 | config.headers["Accept-Language"] = `${locale}`;
67 | }
68 | return await axios.get(url, config);
69 | } catch (error) {
70 | return error.response;
71 | }
72 | }
73 | }
74 |
75 | const axiosJWTDecoratorInstance = new AxiosJWTDecorator();
76 | export default axiosJWTDecoratorInstance;
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/LocalizationCultureProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport
6 | {
7 | using System;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Threading.Tasks;
11 | using Microsoft.AspNetCore.Http;
12 | using Microsoft.AspNetCore.Localization;
13 |
14 | ///
15 | /// The BotLocalizationCultureProvider is responsible for implementing the for Bot Activities
16 | /// received from BotFramework.
17 | ///
18 | internal sealed class LocalizationCultureProvider : IRequestCultureProvider
19 | {
20 | ///
21 | /// Get the culture of the current request.
22 | ///
23 | /// The current request.
24 | /// A Task resolving to the culture info if found, null otherwise.
25 | #pragma warning disable UseAsyncSuffix // Interface method doesn't have Async suffix.
26 | public async Task DetermineProviderCultureResult(HttpContext httpContext)
27 | #pragma warning restore UseAsyncSuffix
28 | {
29 | if (httpContext?.Request?.Body?.CanRead != true)
30 | {
31 | return null;
32 | }
33 |
34 | try
35 | {
36 | // Wrap the request stream so that we can rewind it back to the start for regular request processing.
37 | httpContext.Request.EnableBuffering();
38 |
39 | // Read the request headers and set the parsed culture information.
40 | var locale = httpContext.Request.Headers["Accept-Language"].First()?.Split(',').First();
41 | var result = new ProviderCultureResult(locale);
42 | return result;
43 | }
44 | #pragma warning disable CA1031 // part of the middle ware pipeline, better to use default local then fail the request.
45 | catch (Exception)
46 | #pragma warning restore CA1031
47 | {
48 | return null;
49 | }
50 | finally
51 | {
52 | httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Program.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport
6 | {
7 | using Microsoft.AspNetCore;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.Logging;
11 |
12 | ///
13 | /// The Program class is responsible for holding the entry-point of the program.
14 | ///
15 | public static class Program
16 | {
17 | ///
18 | /// The entry-point for the program.
19 | ///
20 | /// The command line arguments.
21 | public static void Main(string[] args)
22 | {
23 | CreateWebHostBuilder(args).Build().Run();
24 | }
25 |
26 | ///
27 | /// Build the web-host for servicing HTTP requests.
28 | ///
29 | /// The command line arguments.
30 | /// The WebHostBuilder configured from the arguments with the composition root defined in .
31 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
32 | WebHost
33 | .CreateDefaultBuilder(args)
34 | .ConfigureLogging((hostingContext, logging) =>
35 | {
36 | // hostingContext.HostingEnvironment can be used to determine environments as well.
37 | var appInsightKey = hostingContext.Configuration.GetSection("ApplicationInsights")["InstrumentationKey"];
38 | logging.AddApplicationInsights(appInsightKey);
39 | })
40 | .ConfigureAppConfiguration((hostingContext, config) =>
41 | {
42 | config
43 | .AddEnvironmentVariables();
44 |
45 | if (hostingContext.HostingEnvironment.IsDevelopment())
46 | {
47 | // Using dot net secrets to store the settings during development
48 | // https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.0&tabs=windows
49 | config.AddUserSecrets();
50 | }
51 | })
52 | .UseStartup();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/api/remote-support-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import axios from "./axios-decorator";
8 | import { AxiosResponse } from "axios";
9 |
10 | const baseAxiosUrl = window.location.origin;
11 |
12 | /**
13 | * Get all team members.
14 | * @param {String} teamId Team ID for getting members
15 | * @param {String | Null} token Custom JWT token
16 | */
17 | export const getMembersInTeam = async (token: string): Promise => {
18 |
19 | let url = baseAxiosUrl + "/api/remotesupport/teammembers";
20 | let teamMemberResponse = await axios.get(url, token);
21 | return teamMemberResponse;
22 | }
23 |
24 | /**
25 | * Get on call support experts details.
26 | */
27 | export const getOnCallExpertsInTeam = async (token: string): Promise => {
28 |
29 | let url = baseAxiosUrl + "/api/remotesupport/oncallexperts";
30 | let onCallSupportResponse = await axios.get(url, token);
31 | return onCallSupportResponse;
32 | }
33 |
34 | /**
35 | * Get localized resource strings from API
36 | */
37 | export const getResourceStrings = async (token: string, locale?: string | null): Promise => {
38 |
39 | let url = baseAxiosUrl + "/api/resource/resourcestrings";
40 | let resourceStringsResponse = await axios.get(url, token, locale);
41 | return resourceStringsResponse;
42 | }
43 |
44 | /**
45 | * Save on call support details.
46 | * @param {OnCallSupportDetail | Null} onCallSupportDetails On call support details.
47 | * @param {String | Null} token Custom JWT token.
48 | */
49 | export const saveOnCallSupportDetails = async (onCallSupportDetails: any, token: string): Promise => {
50 |
51 | let url = baseAxiosUrl + "/api/remotesupport/saveoncallsupportdetails";
52 | let saveOnCallSupportResponse = await axios.post(url, onCallSupportDetails, token);
53 | return saveOnCallSupportResponse;
54 | }
55 |
56 | /**
57 | * Handle error occurred during API call.
58 | * @param {Object} error Error response object
59 | */
60 | export const handleError = (error: any, token: any): any => {
61 | const errorStatus = error.status;
62 | if (errorStatus === 403) {
63 | window.location.href = "/error?code=403&token=" + token;
64 | }
65 | else if (errorStatus === 401) {
66 | window.location.href = "/error?code=401&token=" + token;
67 | }
68 | else {
69 | window.location.href = "/error?token=" + token;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Helpers/TeamMemberCacheHelper.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Helpers
6 | {
7 | using System;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Microsoft.Bot.Builder;
11 | using Microsoft.Bot.Builder.Teams;
12 | using Microsoft.Bot.Schema.Teams;
13 | using Microsoft.Extensions.Caching.Memory;
14 |
15 | ///
16 | /// Class that handles the card configuration.
17 | ///
18 | public static class TeamMemberCacheHelper
19 | {
20 | ///
21 | /// Cache key for expert details
22 | ///
23 | private const string ExpertCollectionCacheKey = "_expertCollectionKey";
24 |
25 | ///
26 | /// Sets the team members cache duration.
27 | ///
28 | private static readonly TimeSpan CacheDuration = TimeSpan.FromDays(1);
29 |
30 | ///
31 | /// Provide team members information.
32 | ///
33 | /// MemoryCache instance for caching on call expert objectId's.
34 | /// Provides context for a turn of a bot.
35 | /// Describes a user Id.
36 | /// Describes a team Id.
37 | /// Propagates notification that operations should be canceled.
38 | /// Returns team members information from cache.
39 | public static async Task GetMemberInfoAsync(IMemoryCache memoryCache, ITurnContext turnContext, string userId, string teamId, CancellationToken cancellationToken)
40 | {
41 | bool isCacheEntryExists = memoryCache.TryGetValue(ExpertCollectionCacheKey + userId, out TeamsChannelAccount memberInformation);
42 |
43 | if (!isCacheEntryExists)
44 | {
45 | if (teamId != null)
46 | {
47 | memberInformation = await TeamsInfo.GetTeamMemberAsync(turnContext, userId, teamId);
48 | }
49 | else
50 | {
51 | memberInformation = await TeamsInfo.GetMemberAsync(turnContext, userId, cancellationToken);
52 | }
53 |
54 | if (memberInformation != null)
55 | {
56 | memoryCache.Set(ExpertCollectionCacheKey + userId, memberInformation, CacheDuration);
57 | }
58 | }
59 |
60 | return memberInformation;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Models/OnCallSupportDetail.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Models
6 | {
7 | using System;
8 | using System.ComponentModel.DataAnnotations;
9 | using Microsoft.Azure.Search;
10 | using Microsoft.WindowsAzure.Storage.Table;
11 | using Newtonsoft.Json;
12 |
13 | ///
14 | /// Class contains details of on call support team.
15 | ///
16 | public class OnCallSupportDetail : TableEntity
17 | {
18 | ///
19 | /// Initializes a new instance of the class.
20 | /// Constructor method used to initialize partition key of table.
21 | ///
22 | public OnCallSupportDetail()
23 | {
24 | this.PartitionKey = Constants.OnCallSupportDetailPartitionKey;
25 | }
26 |
27 | ///
28 | /// Gets or sets unique identifier of the on call support created.
29 | ///
30 | [Key]
31 | [JsonProperty("OnCallSupportId")]
32 | public string OnCallSupportId
33 | {
34 | get { return this.RowKey; }
35 | set { this.RowKey = value; }
36 | }
37 |
38 | ///
39 | /// Gets time stamp from storage table.
40 | ///
41 | [IsSortable]
42 | [JsonProperty("Timestamp")]
43 | public new DateTimeOffset Timestamp => base.Timestamp;
44 |
45 | ///
46 | /// Gets or sets name of user who modified on call support experts list.
47 | ///
48 | [IsSearchable]
49 | [IsFilterable]
50 | [JsonProperty("ModifiedByName")]
51 | public string ModifiedByName { get; set; }
52 |
53 | ///
54 | /// Gets or sets AAD object id of user who modified on call support experts list.
55 | ///
56 | [IsSearchable]
57 | [IsFilterable]
58 | [JsonProperty("ModifiedByObjectId")]
59 | public string ModifiedByObjectId { get; set; }
60 |
61 | ///
62 | /// Gets or sets date on which on call support experts list was updated.
63 | ///
64 | [IsSortable]
65 | [IsFilterable]
66 | [JsonProperty("ModifiedOn")]
67 | public DateTime? ModifiedOn { get; set; }
68 |
69 | ///
70 | /// Gets or sets on call support experts details in json string.
71 | ///
72 | [IsSearchable]
73 | [IsFilterable]
74 | [JsonProperty("OnCallSMEs")]
75 | public string OnCallSMEs { get; set; }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Bot/RemoteSupportAdapterWithErrorHandler.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Bot
6 | {
7 | using System;
8 | using Microsoft.Bot.Builder;
9 | using Microsoft.Bot.Builder.Integration.AspNet.Core;
10 | using Microsoft.Extensions.Configuration;
11 | using Microsoft.Extensions.Localization;
12 | using Microsoft.Extensions.Logging;
13 |
14 | ///
15 | /// Implements Error Handler.
16 | ///
17 | public class RemoteSupportAdapterWithErrorHandler : BotFrameworkHttpAdapter
18 | {
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | /// Application configurations.
23 | /// Instance to send logs to the Application Insights service.
24 | /// Represents middle ware that can operate on incoming activities.
25 | /// The current cultures' string localizer.
26 | /// conversationState.
27 | public RemoteSupportAdapterWithErrorHandler(IConfiguration configuration, ILogger logger, RemotesupportActivityMiddleWare remoteSupportActivityMiddleWare, IStringLocalizer localizer, ConversationState conversationState = null)
28 | : base(configuration)
29 | {
30 | if (remoteSupportActivityMiddleWare == null)
31 | {
32 | throw new NullReferenceException(nameof(remoteSupportActivityMiddleWare));
33 | }
34 |
35 | // Add activity middle ware to the adapter's middle ware pipeline
36 | this.Use(remoteSupportActivityMiddleWare);
37 |
38 | this.OnTurnError = async (turnContext, exception) =>
39 | {
40 | // Log any leaked exception from the application.
41 | logger.LogError(exception, $"Exception caught : {exception.Message}");
42 |
43 | // Send a catch-all apology to the user.
44 | await turnContext.SendActivityAsync(localizer.GetString("ErrorMessage"));
45 |
46 | if (conversationState != null)
47 | {
48 | try
49 | {
50 | // Delete the conversationState for the current conversation to prevent the
51 | // bot from getting stuck in a error-loop caused by being in a bad state.
52 | // ConversationState should be thought of as similar to "cookie-state" in a Web pages.
53 | await conversationState.DeleteAsync(turnContext);
54 | }
55 | catch (Exception ex)
56 | {
57 | logger.LogError(ex, $"Exception caught on attempting to Delete ConversationState : {ex.Message}");
58 | }
59 | }
60 | };
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/deploy.bot.cmd:
--------------------------------------------------------------------------------
1 | @if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off
2 |
3 | :: ----------------------
4 | :: KUDU Deployment Script
5 | :: Version: 1.0.17
6 | :: ----------------------
7 |
8 | :: Prerequisites
9 | :: -------------
10 |
11 | :: Verify node.js installed
12 | where node 2>nul >nul
13 | IF %ERRORLEVEL% NEQ 0 (
14 | echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
15 | goto error
16 | )
17 |
18 | :: Setup
19 | :: -----
20 |
21 | setlocal enabledelayedexpansion
22 |
23 | SET ARTIFACTS=%~dp0%..\artifacts
24 |
25 | IF NOT DEFINED DEPLOYMENT_SOURCE (
26 | SET DEPLOYMENT_SOURCE=%~dp0%.
27 | )
28 |
29 | IF NOT DEFINED DEPLOYMENT_TARGET (
30 | SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
31 | )
32 |
33 | IF NOT DEFINED NEXT_MANIFEST_PATH (
34 | SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest
35 |
36 | IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
37 | SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest
38 | )
39 | )
40 |
41 | IF NOT DEFINED KUDU_SYNC_CMD (
42 | :: Install kudu sync
43 | echo Installing Kudu Sync
44 | call npm install kudusync -g --silent
45 | IF !ERRORLEVEL! NEQ 0 goto error
46 |
47 | :: Locally just running "kuduSync" would also work
48 | SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd
49 | )
50 | IF NOT DEFINED DEPLOYMENT_TEMP (
51 | SET DEPLOYMENT_TEMP=%temp%\___deployTemp%random%
52 | SET CLEAN_LOCAL_DEPLOYMENT_TEMP=true
53 | )
54 |
55 | IF DEFINED CLEAN_LOCAL_DEPLOYMENT_TEMP (
56 | IF EXIST "%DEPLOYMENT_TEMP%" rd /s /q "%DEPLOYMENT_TEMP%"
57 | mkdir "%DEPLOYMENT_TEMP%"
58 | )
59 |
60 | IF DEFINED MSBUILD_PATH goto MsbuildPathDefined
61 | SET MSBUILD_PATH=%ProgramFiles(x86)%\MSBuild\14.0\Bin\MSBuild.exe
62 | :MsbuildPathDefined
63 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
64 | :: Deployment
65 | :: ----------
66 |
67 | echo Handling ASP.NET Core Web Application deployment.
68 |
69 | :: 1. Restore nuget packages
70 | call :ExecuteCmd dotnet restore "%DEPLOYMENT_SOURCE%\Source\Microsoft.Teams.Apps.RemoteSupport.sln"
71 | IF !ERRORLEVEL! NEQ 0 goto error
72 |
73 | :: 2. Build and publish
74 | call :ExecuteCmd dotnet publish "%DEPLOYMENT_SOURCE%\Source\RemoteSupport\Microsoft.Teams.Apps.RemoteSupport.csproj" --output "%DEPLOYMENT_TEMP%" --configuration Release
75 | IF !ERRORLEVEL! NEQ 0 goto error
76 |
77 | :: 3. KuduSync
78 | call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_TEMP%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
79 | IF !ERRORLEVEL! NEQ 0 goto error
80 |
81 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
82 | goto end
83 |
84 | :: Execute command routine that will echo out when error
85 | :ExecuteCmd
86 | setlocal
87 | set _CMD_=%*
88 | call %_CMD_%
89 | if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
90 | exit /b %ERRORLEVEL%
91 |
92 | :error
93 | endlocal
94 | echo An error has occurred during web site deployment.
95 | call :exitSetErrorLevel
96 | call :exitFromFunction 2>nul
97 |
98 | :exitSetErrorLevel
99 | exit /b 1
100 |
101 | :exitFromFunction
102 | ()
103 |
104 | :end
105 | endlocal
106 | echo Finished successfully.
107 |
--------------------------------------------------------------------------------
/deploy.configuration.cmd:
--------------------------------------------------------------------------------
1 | @if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off
2 |
3 | :: ----------------------
4 | :: KUDU Deployment Script
5 | :: Version: 1.0.17
6 | :: ----------------------
7 |
8 | :: Prerequisites
9 | :: -------------
10 |
11 | :: Verify node.js installed
12 | where node 2>nul >nul
13 | IF %ERRORLEVEL% NEQ 0 (
14 | echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
15 | goto error
16 | )
17 |
18 | :: Setup
19 | :: -----
20 |
21 | setlocal enabledelayedexpansion
22 |
23 | SET ARTIFACTS=%~dp0%..\artifacts
24 |
25 | IF NOT DEFINED DEPLOYMENT_SOURCE (
26 | SET DEPLOYMENT_SOURCE=%~dp0%.
27 | )
28 |
29 | IF NOT DEFINED DEPLOYMENT_TARGET (
30 | SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
31 | )
32 |
33 | IF NOT DEFINED NEXT_MANIFEST_PATH (
34 | SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest
35 |
36 | IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
37 | SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest
38 | )
39 | )
40 |
41 | IF NOT DEFINED KUDU_SYNC_CMD (
42 | :: Install kudu sync
43 | echo Installing Kudu Sync
44 | call npm install kudusync -g --silent
45 | IF !ERRORLEVEL! NEQ 0 goto error
46 |
47 | :: Locally just running "kuduSync" would also work
48 | SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd
49 | )
50 | IF NOT DEFINED DEPLOYMENT_TEMP (
51 | SET DEPLOYMENT_TEMP=%temp%\___deployTemp%random%
52 | SET CLEAN_LOCAL_DEPLOYMENT_TEMP=true
53 | )
54 |
55 | IF DEFINED CLEAN_LOCAL_DEPLOYMENT_TEMP (
56 | IF EXIST "%DEPLOYMENT_TEMP%" rd /s /q "%DEPLOYMENT_TEMP%"
57 | mkdir "%DEPLOYMENT_TEMP%"
58 | )
59 |
60 | IF DEFINED MSBUILD_PATH goto MsbuildPathDefined
61 | SET MSBUILD_PATH=%ProgramFiles(x86)%\MSBuild\14.0\Bin\MSBuild.exe
62 | :MsbuildPathDefined
63 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
64 | :: Deployment
65 | :: ----------
66 |
67 | echo Handling ASP.NET Core Web Application deployment.
68 |
69 | :: 1. Restore nuget packages
70 | call :ExecuteCmd dotnet restore "%DEPLOYMENT_SOURCE%\Source\Microsoft.Teams.Apps.RemoteSupport.sln"
71 | IF !ERRORLEVEL! NEQ 0 goto error
72 |
73 | :: 2. Build and publish
74 | call :ExecuteCmd dotnet publish "%DEPLOYMENT_SOURCE%\Source\RemoteSupport.Configuration\Microsoft.Teams.Apps.RemoteSupport.Configuration.csproj" --output "%DEPLOYMENT_TEMP%" --configuration Release
75 | IF !ERRORLEVEL! NEQ 0 goto error
76 |
77 | :: 3. KuduSync
78 | call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_TEMP%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
79 | IF !ERRORLEVEL! NEQ 0 goto error
80 |
81 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
82 | goto end
83 |
84 | :: Execute command routine that will echo out when error
85 | :ExecuteCmd
86 | setlocal
87 | set _CMD_=%*
88 | call %_CMD_%
89 | if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
90 | exit /b %ERRORLEVEL%
91 |
92 | :error
93 | endlocal
94 | echo An error has occurred during web site deployment.
95 | call :exitSetErrorLevel
96 | call :exitFromFunction 2>nul
97 |
98 | :exitSetErrorLevel
99 | exit /b 1
100 |
101 | :exitFromFunction
102 | ()
103 |
104 | :end
105 | endlocal
106 | echo Finished successfully.
107 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Controllers/ResourceController.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Controllers
6 | {
7 | using System;
8 | using Microsoft.AspNetCore.Authorization;
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.Extensions.Localization;
12 | using Microsoft.Extensions.Logging;
13 |
14 | ///
15 | /// Controller to handle resource strings related request.
16 | ///
17 | [Route("api/resource")]
18 | [Authorize]
19 | [ApiController]
20 | public class ResourceController : ControllerBase
21 | {
22 | ///
23 | /// Sends logs to the Application Insights service.
24 | ///
25 | private readonly ILogger logger;
26 |
27 | ///
28 | /// The current cultures' string localizer.
29 | ///
30 | private readonly IStringLocalizer localizer;
31 |
32 | ///
33 | /// Initializes a new instance of the class.
34 | ///
35 | /// Instance to send logs to the Application Insights service.
36 | /// The current cultures' string localizer.
37 | public ResourceController(ILogger logger, IStringLocalizer localizer)
38 | {
39 | this.logger = logger;
40 | this.localizer = localizer;
41 | }
42 |
43 | ///
44 | /// Get resource strings for displaying in client application.
45 | ///
46 | /// Resource strings according to user locale.
47 | [Route("resourcestrings")]
48 | public IActionResult GetResourceStrings()
49 | {
50 | try
51 | {
52 | var strings = new
53 | {
54 | AddButtonText = this.localizer.GetString("AddButtonText").Value,
55 | SaveButtonText = this.localizer.GetString("SaveButtonText").Value,
56 | ExpertListTitle = this.localizer.GetString("ExpertListTitle").Value,
57 | ExpertNameTitle = this.localizer.GetString("ExpertNameTitle").Value,
58 | NoMatchesFoundText = this.localizer.GetString("NoMatchesFoundText").Value,
59 | ExpertListPlaceHolderText = this.localizer.GetString("ExpertListPlaceHolderText").Value,
60 | ErrorMessage = this.localizer.GetString("ErrorMessage").Value,
61 | MaxOnCallExpertsAllowedText = this.localizer.GetString("MaxOnCallExpertsAllowedText").Value,
62 | UnauthorizedAccess = this.localizer.GetString("UnauthorizedAccess").Value,
63 | SessionExpired = this.localizer.GetString("SessionExpired").Value,
64 | };
65 | return this.Ok(strings);
66 | }
67 | #pragma warning disable CA1031 // Do not catch general exception types
68 | catch (Exception ex)
69 | #pragma warning restore CA1031 // Do not catch general exception types
70 | {
71 | this.logger.LogError(ex, "Error while fetching resource strings.");
72 | return this.StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/TicketDetailStorageProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System.Net;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Options;
10 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
11 | using Microsoft.WindowsAzure.Storage.Table;
12 |
13 | ///
14 | /// Ticket provider helps in fetching and storing information in storage table.
15 | ///
16 | public class TicketDetailStorageProvider : StorageBaseProvider, ITicketDetailStorageProvider
17 | {
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | /// A set of key/value application storage configuration properties.
22 | public TicketDetailStorageProvider(IOptionsMonitor storageOptions)
23 | : base(storageOptions, Constants.TicketDetailTable)
24 | {
25 | }
26 |
27 | ///
28 | /// Store or update ticket entity in table storage.
29 | ///
30 | /// Represents ticket entity used for storage and retrieval.
31 | /// that represents configuration entity is saved or updated.
32 | public async Task UpsertTicketAsync(TicketDetail ticketDetails)
33 | {
34 | var result = await this.StoreOrUpdateTicketEntityAsync(ticketDetails);
35 | return result.HttpStatusCode == (int)HttpStatusCode.NoContent;
36 | }
37 |
38 | ///
39 | /// Get already saved entity detail from storage table.
40 | ///
41 | /// ticket id received from bot based on which appropriate row data will be fetched.
42 | /// Already saved entity detail.
43 | public async Task GetTicketAsync(string ticketId)
44 | {
45 | await this.EnsureInitializedAsync(); // When there is no ticket created by end user and messaging extension is open by SME, table initialization is required before creating search index or data source or indexer.
46 | if (string.IsNullOrEmpty(ticketId))
47 | {
48 | return null;
49 | }
50 |
51 | var searchOperation = TableOperation.Retrieve(Constants.TicketDetailPartitionKey, ticketId);
52 | var searchResult = await this.CloudTable.ExecuteAsync(searchOperation);
53 |
54 | return (TicketDetail)searchResult.Result;
55 | }
56 |
57 | ///
58 | /// Store or update ticket entity in table storage.
59 | ///
60 | /// Represents ticket entity used for storage and retrieval.
61 | /// that represents ticket entity is saved or updated.
62 | private async Task StoreOrUpdateTicketEntityAsync(TicketDetail ticketDetails)
63 | {
64 | await this.EnsureInitializedAsync();
65 | TableOperation addOrUpdateOperation = TableOperation.InsertOrReplace(ticketDetails);
66 | var result = await this.CloudTable.ExecuteAsync(addOrUpdateOperation);
67 | return result;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Controllers/BaseRemoteSupportController.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Controllers
6 | {
7 | using System.Linq;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.Teams.Apps.RemoteSupport.Models;
11 |
12 | ///
13 | /// Base controller to handle user and company response API operations.
14 | ///
15 | [Route("api/[controller]")]
16 | [ApiController]
17 | public class BaseRemoteSupportController : ControllerBase
18 | {
19 | ///
20 | /// Get claims of user.
21 | ///
22 | /// User claims.
23 | protected JwtClaims GetUserClaims()
24 | {
25 | var claims = this.User.Claims;
26 | var jwtClaims = new JwtClaims
27 | {
28 | FromId = claims.Where(claim => claim.Type == "fromId").Select(claim => claim.Value).First(),
29 | ApplicationBasePath = claims.Where(claim => claim.Type == "applicationBasePath").Select(claim => claim.Value).First(),
30 | };
31 |
32 | return jwtClaims;
33 | }
34 |
35 | ///
36 | /// Method checks if user is authorized to make API calls.
37 | ///
38 | /// Returns success/failure depending on whether user is authorized.
39 | protected bool IsUserAuthenticated()
40 | {
41 | var fromId = this.User.Claims.Where(claim => claim.Type == "fromId").Select(claim => claim.Value).FirstOrDefault();
42 | if (string.IsNullOrEmpty(fromId))
43 | {
44 | return false;
45 | }
46 |
47 | return true;
48 | }
49 |
50 | ///
51 | /// Creates the error response as per the status codes in case of error.
52 | ///
53 | /// Describes the type of error.
54 | /// Describes the error message.
55 | /// Returns error response with appropriate message and status code.
56 | protected IActionResult GetErrorResponse(int statusCode, string errorMessage)
57 | {
58 | switch (statusCode)
59 | {
60 | case StatusCodes.Status401Unauthorized:
61 | return this.StatusCode(
62 | StatusCodes.Status401Unauthorized,
63 | new ErrorResponse
64 | {
65 | StatusCode = "signinRequired",
66 | ErrorMessage = errorMessage,
67 | });
68 | case StatusCodes.Status400BadRequest:
69 | return this.StatusCode(
70 | StatusCodes.Status400BadRequest,
71 | new ErrorResponse
72 | {
73 | StatusCode = "badRequest",
74 | ErrorMessage = errorMessage,
75 | });
76 | default:
77 | return this.StatusCode(
78 | StatusCodes.Status500InternalServerError,
79 | new ErrorResponse
80 | {
81 | StatusCode = "internalServerError",
82 | ErrorMessage = errorMessage,
83 | });
84 | }
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Bot/BotLocalizationCultureProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport
6 | {
7 | using System;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 | using Microsoft.AspNetCore.Http;
13 | using Microsoft.AspNetCore.Localization;
14 | using Microsoft.Bot.Schema;
15 | using Newtonsoft.Json;
16 | using Newtonsoft.Json.Linq;
17 |
18 | ///
19 | /// The BotLocalizationCultureProvider is responsible for implementing the for Bot Activities
20 | /// received from BotFramework.
21 | ///
22 | internal sealed class BotLocalizationCultureProvider : IRequestCultureProvider
23 | {
24 | ///
25 | /// Get the culture of the current request.
26 | ///
27 | /// The current request.
28 | /// A Task resolving to the culture info if found, null otherwise.
29 | #pragma warning disable UseAsyncSuffix // Interface method doesn't have Async suffix.
30 | public async Task DetermineProviderCultureResult(HttpContext httpContext)
31 | #pragma warning restore UseAsyncSuffix
32 | {
33 | if (httpContext?.Request?.Body?.CanRead != true)
34 | {
35 | return null;
36 | }
37 |
38 | string locale = string.Empty;
39 | var isBotFrameworkUserAgent =
40 | httpContext.Request.Headers["User-Agent"]
41 | .Any(userAgent => userAgent.Contains("Microsoft-BotFramework", StringComparison.OrdinalIgnoreCase));
42 |
43 | if (!isBotFrameworkUserAgent)
44 | {
45 | locale = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
46 | locale = locale?.Split(",")?.FirstOrDefault();
47 | if (string.IsNullOrEmpty(locale))
48 | {
49 | return null;
50 | }
51 | }
52 |
53 | try
54 | {
55 | if (isBotFrameworkUserAgent)
56 | {
57 | // Wrap the request stream so that we can rewind it back to the start for regular request processing.
58 | httpContext.Request.EnableBuffering();
59 |
60 | // Read the request body, parse out the activity object, and set the parsed culture information.
61 | using (var streamReader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, leaveOpen: true))
62 | using (var jsonReader = new JsonTextReader(streamReader))
63 | {
64 | var obj = await JObject.LoadAsync(jsonReader);
65 | var activity = obj.ToObject();
66 | var result = new ProviderCultureResult(activity.Locale);
67 | httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
68 | return result;
69 | }
70 | }
71 | else
72 | {
73 | var result = new ProviderCultureResult(locale);
74 | return result;
75 | }
76 | }
77 | #pragma warning disable CA1031 // part of the middle ware pipeline, better to use default local then fail the request.
78 | catch (Exception)
79 | #pragma warning restore CA1031
80 | {
81 | return null;
82 | }
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/StorageBaseProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Options;
10 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
11 | using Microsoft.WindowsAzure.Storage;
12 | using Microsoft.WindowsAzure.Storage.RetryPolicies;
13 | using Microsoft.WindowsAzure.Storage.Table;
14 |
15 | ///
16 | /// Class handles initialization to Azure table storage.
17 | ///
18 | public class StorageBaseProvider
19 | {
20 | ///
21 | /// Azure storage table name to perform operations.
22 | ///
23 | private readonly string tableName;
24 |
25 | ///
26 | /// Connection string of azure table storage.
27 | ///
28 | private readonly string connectionString;
29 |
30 | ///
31 | /// A lazy task to initialize Azure table storage.
32 | ///
33 | private readonly Lazy initializeTask;
34 |
35 | ///
36 | /// Azure cloud table client.
37 | ///
38 | private CloudTableClient cloudTableClient;
39 |
40 | ///
41 | /// Initializes a new instance of the class.
42 | ///
43 | /// A set of key/value application storage configuration properties.
44 | /// Table name of azure table storage to initialize.
45 | public StorageBaseProvider(IOptionsMonitor storageOptions, string tableName)
46 | {
47 | storageOptions = storageOptions ?? throw new ArgumentNullException(nameof(storageOptions));
48 | this.connectionString = storageOptions.CurrentValue.ConnectionString;
49 | this.tableName = tableName;
50 | this.initializeTask = new Lazy(() => this.InitializeAsync());
51 | }
52 |
53 | ///
54 | /// Gets or sets cloud table for storing group activity and details regarding sending notification.
55 | ///
56 | protected CloudTable CloudTable { get; set; }
57 |
58 | ///
59 | /// Ensures Microsoft Azure Table Storage should be created before working on table.
60 | ///
61 | /// Represents an asynchronous operation.
62 | protected async Task EnsureInitializedAsync()
63 | {
64 | await this.initializeTask.Value;
65 | }
66 |
67 | ///
68 | /// Create storage table if it does not exist.
69 | ///
70 | /// representing the asynchronous operation task which represents table is created if it does not exists.
71 | private async Task InitializeAsync()
72 | {
73 | // Exponential retry policy with back off set to 3 seconds and 5 retries.
74 | var exponentialRetryPolicy = new TableRequestOptions()
75 | {
76 | RetryPolicy = new ExponentialRetry(TimeSpan.FromSeconds(3), 5),
77 | };
78 |
79 | CloudStorageAccount storageAccount = CloudStorageAccount.Parse(this.connectionString);
80 | this.cloudTableClient = storageAccount.CreateCloudTableClient();
81 | this.cloudTableClient.DefaultRequestOptions = exponentialRetryPolicy;
82 | this.CloudTable = this.cloudTableClient.GetTableReference(this.tableName);
83 |
84 | await this.CloudTable.CreateIfNotExistsAsync();
85 |
86 | return this.CloudTable;
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Helpers/TokenHelper.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Helpers
6 | {
7 | using System;
8 | using System.Collections.Generic;
9 | using System.IdentityModel.Tokens.Jwt;
10 | using System.Security.Claims;
11 | using System.Text;
12 | using Microsoft.Extensions.Options;
13 | using Microsoft.IdentityModel.Tokens;
14 | using Microsoft.Teams.Apps.RemoteSupport;
15 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
16 |
17 | ///
18 | /// Helper class for JWT token generation and validation.
19 | ///
20 | public class TokenHelper : ITokenHelper
21 | {
22 | ///
23 | /// Security key for generating and validating token.
24 | ///
25 | private readonly string securityKey;
26 |
27 | ///
28 | /// Application base URL.
29 | ///
30 | private readonly string appBaseUri;
31 |
32 | ///
33 | /// Initializes a new instance of the class.
34 | ///
35 | /// A set of key/value application configuration properties for Remote Support bot.
36 | /// A set of key/value application configuration properties for token.
37 | public TokenHelper(
38 | IOptionsMonitor remoteSupportActivityHandlerOptions,
39 | IOptionsMonitor tokenOptions)
40 | {
41 | tokenOptions = tokenOptions ?? throw new ArgumentNullException(nameof(tokenOptions));
42 | remoteSupportActivityHandlerOptions = remoteSupportActivityHandlerOptions ?? throw new ArgumentNullException(nameof(remoteSupportActivityHandlerOptions));
43 | this.securityKey = tokenOptions.CurrentValue.SecurityKey;
44 | this.appBaseUri = remoteSupportActivityHandlerOptions.CurrentValue.AppBaseUri;
45 | }
46 |
47 | ///
48 | /// Generate JWT token used by client application to authenticate HTTP calls with API.
49 | ///
50 | /// Service URL from bot.
51 | /// Unique Id from activity.
52 | /// Expiry of token.
53 | /// JWT token.
54 | public string GenerateAPIAuthToken(string applicationBasePath, string fromId, int jwtExpiryMinutes)
55 | {
56 | SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(this.securityKey));
57 | SigningCredentials signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
58 |
59 | SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor()
60 | {
61 | Subject = new ClaimsIdentity(
62 | new List()
63 | {
64 | new Claim("applicationBasePath", applicationBasePath),
65 | new Claim("fromId", fromId),
66 | }, "Custom"),
67 | NotBefore = DateTime.UtcNow,
68 | SigningCredentials = signingCredentials,
69 | Issuer = this.appBaseUri,
70 | Audience = this.appBaseUri,
71 | IssuedAt = DateTime.UtcNow,
72 | Expires = DateTime.UtcNow.AddMinutes(jwtExpiryMinutes),
73 | };
74 |
75 | JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
76 | SecurityToken token = tokenHandler.CreateToken(securityTokenDescriptor);
77 | return tokenHandler.WriteToken(token);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Common/Providers/OnCallSupportDetailStorageProvider.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Common.Providers
6 | {
7 | using System;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Options;
11 | using Microsoft.Teams.Apps.RemoteSupport.Common.Models;
12 | using Microsoft.WindowsAzure.Storage.Table;
13 |
14 | ///
15 | /// On call support detail provider helps in fetching and storing information in storage table.
16 | ///
17 | public class OnCallSupportDetailStorageProvider : StorageBaseProvider, IOnCallSupportDetailStorageProvider
18 | {
19 | private readonly ILogger logger;
20 |
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// A set of key/value application storage configuration properties.
25 | /// Instance to send logs to the Application Insights service.
26 | public OnCallSupportDetailStorageProvider(IOptionsMonitor storageOptions, ILogger logger)
27 | : base(storageOptions, Constants.OnCallSupportDetailTable)
28 | {
29 | this.logger = logger;
30 | }
31 |
32 | ///
33 | /// Save on call support details in Azure Table Storage.
34 | ///
35 | /// On call support details to be stored in table storage.
36 | /// Returns OnCallSupportId when on call support data was saved successfully.
37 | public async Task UpsertOnCallSupportDetailsAsync(OnCallSupportDetail onCallSupportTeamDetails)
38 | {
39 | await this.EnsureInitializedAsync();
40 | onCallSupportTeamDetails = onCallSupportTeamDetails ?? throw new ArgumentNullException(nameof(onCallSupportTeamDetails));
41 | onCallSupportTeamDetails.RowKey = Guid.NewGuid().ToString();
42 | TableOperation addOperation = TableOperation.Insert(onCallSupportTeamDetails);
43 | var result = await this.CloudTable.ExecuteAsync(addOperation);
44 | if (result.Result != null)
45 | {
46 | var onCallSMEDetails = (OnCallSupportDetail)result.Result;
47 | return onCallSMEDetails.OnCallSupportId;
48 | }
49 |
50 | return string.Empty;
51 | }
52 |
53 | ///
54 | /// Get already saved entity detail from storage table.
55 | ///
56 | /// onCallSMEId received from bot based on which appropriate row data will be fetched.
57 | /// Already saved entity detail.
58 | public async Task GetOnCallSupportDetailAsync(string onCallSMEId)
59 | {
60 | await this.EnsureInitializedAsync(); // When there is no on call support details added and task module is opened by SME, table initialization is required before creating search index or data source or indexer.
61 | if (string.IsNullOrEmpty(onCallSMEId))
62 | {
63 | this.logger.LogInformation("There are no on call experts configured.");
64 | return null;
65 | }
66 |
67 | var searchOperation = TableOperation.Retrieve(Constants.OnCallSupportDetailPartitionKey, onCallSMEId);
68 | var searchResult = await this.CloudTable.ExecuteAsync(searchOperation);
69 |
70 | return (OnCallSupportDetail)searchResult.Result;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Manifest/EndUser/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "manifestVersion": "1.5",
4 | "version": "1.0.0",
5 | "id": "91687436-afbb-4856-ab94-ca88fd5e2590",
6 | "packageName": "com.microsoft.teams.remotesupport.users",
7 | "developer": {
8 | "name": "<>",
9 | "websiteUrl": "<>",
10 | "privacyUrl": "<>",
11 | "termsOfUseUrl": "<>"
12 | },
13 | "localizationInfo": {
14 | "defaultLanguageTag": "en",
15 | "additionalLanguages": [
16 | {
17 | "languageTag": "de",
18 | "file": "de.json"
19 | },
20 | {
21 | "languageTag": "en",
22 | "file": "en.json"
23 | },
24 | {
25 | "languageTag": "fr",
26 | "file": "fr.json"
27 | },
28 | {
29 | "languageTag": "ar",
30 | "file": "ar.json"
31 | },
32 | {
33 | "languageTag": "ja",
34 | "file": "ja.json"
35 | },
36 | {
37 | "languageTag": "es",
38 | "file": "es.json"
39 | },
40 | {
41 | "languageTag": "he",
42 | "file": "he.json"
43 | },
44 | {
45 | "languageTag": "ko",
46 | "file": "ko.json"
47 | },
48 | {
49 | "languageTag": "pt-BR",
50 | "file": "pt-BR.json"
51 | },
52 | {
53 | "languageTag": "ru",
54 | "file": "ru.json"
55 | },
56 | {
57 | "languageTag": "zh-CN",
58 | "file": "zh-CN.json"
59 | },
60 | {
61 | "languageTag": "zh-TW",
62 | "file": "zh-TW.json"
63 | }
64 | ]
65 | },
66 | "icons": {
67 | "color": "color.png",
68 | "outline": "outline.png"
69 | },
70 | "name": {
71 | "short": "Remote Support"
72 | },
73 | "description": {
74 | "short": "Helps request support and connect with experts quickly",
75 | "full": "Request remote support quickly. Search requests and escalate to on call experts via group chat for urgent requests."
76 | },
77 | "accentColor": "#64A2CC",
78 | "bots": [
79 | {
80 | "botId": "<>",
81 | "scopes": [
82 | "personal"
83 | ],
84 | "commandLists": [
85 | {
86 | "scopes": [
87 | "personal"
88 | ],
89 | "commands": [
90 | {
91 | "title": "New request",
92 | "description": "Make a request to the on-call team"
93 | }
94 | ]
95 | }
96 | ],
97 | "supportsFiles": false,
98 | "isNotificationOnly": false
99 | }
100 | ],
101 | "composeExtensions": [
102 | {
103 | "botId": "<>",
104 | "canUpdateConfiguration": false,
105 | "commands": [
106 | {
107 | "id": "activerequests",
108 | "type": "query",
109 | "title": "Active",
110 | "description": "Search active requests",
111 | "parameters": [
112 | {
113 | "name": "searchText",
114 | "title": "Search",
115 | "description": "Search requests"
116 | }
117 | ],
118 | "context": [
119 | "compose",
120 | "commandBox"
121 | ],
122 | "initialRun": true
123 | },
124 | {
125 | "id": "closedrequests",
126 | "type": "query",
127 | "title": "Closed",
128 | "description": "Search closed requests",
129 | "parameters": [
130 | {
131 | "name": "searchText",
132 | "title": "Search",
133 | "description": "Search requests"
134 | }
135 | ],
136 | "context": [
137 | "compose",
138 | "commandBox"
139 | ],
140 | "initialRun": true
141 | }
142 | ]
143 | }
144 | ],
145 | "permissions": [
146 | "identity",
147 | "messageTeamMembers"
148 | ],
149 | "validDomains": [
150 | "<>"
151 | ]
152 | }
--------------------------------------------------------------------------------
/Source/Microsoft.Teams.Apps.RemoteSupport.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29728.190
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Teams.Apps.RemoteSupport", "RemoteSupport\Microsoft.Teams.Apps.RemoteSupport.csproj", "{90AA7097-ABD6-4D38-9EA2-D9E613146E1E}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Teams.Apps.RemoteSupport.Common", "RemoteSupport.Common\Microsoft.Teams.Apps.RemoteSupport.Common.csproj", "{70491F0E-05FA-4438-B026-7084868BA8C3}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Teams.Apps.RemoteSupport.Configuration", "RemoteSupport.Configuration\Microsoft.Teams.Apps.RemoteSupport.Configuration.csproj", "{7A229EB9-602D-452D-A736-F7CFB19DFDFD}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|x64 = Debug|x64
16 | Debug|x86 = Debug|x86
17 | Release|Any CPU = Release|Any CPU
18 | Release|x64 = Release|x64
19 | Release|x86 = Release|x86
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Debug|x64.ActiveCfg = Debug|Any CPU
25 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Debug|x64.Build.0 = Debug|Any CPU
26 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Debug|x86.ActiveCfg = Debug|Any CPU
27 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Debug|x86.Build.0 = Debug|Any CPU
28 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Release|x64.ActiveCfg = Release|Any CPU
31 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Release|x64.Build.0 = Release|Any CPU
32 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Release|x86.ActiveCfg = Release|Any CPU
33 | {90AA7097-ABD6-4D38-9EA2-D9E613146E1E}.Release|x86.Build.0 = Release|Any CPU
34 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Debug|x64.ActiveCfg = Debug|Any CPU
37 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Debug|x64.Build.0 = Debug|Any CPU
38 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Debug|x86.ActiveCfg = Debug|Any CPU
39 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Debug|x86.Build.0 = Debug|Any CPU
40 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Release|x64.ActiveCfg = Release|Any CPU
43 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Release|x64.Build.0 = Release|Any CPU
44 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Release|x86.ActiveCfg = Release|Any CPU
45 | {70491F0E-05FA-4438-B026-7084868BA8C3}.Release|x86.Build.0 = Release|Any CPU
46 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Debug|x64.ActiveCfg = Debug|Any CPU
49 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Debug|x64.Build.0 = Debug|Any CPU
50 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Debug|x86.ActiveCfg = Debug|Any CPU
51 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Debug|x86.Build.0 = Debug|Any CPU
52 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Release|x64.ActiveCfg = Release|Any CPU
55 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Release|x64.Build.0 = Release|Any CPU
56 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Release|x86.ActiveCfg = Release|Any CPU
57 | {7A229EB9-602D-452D-A736-F7CFB19DFDFD}.Release|x86.Build.0 = Release|Any CPU
58 | EndGlobalSection
59 | GlobalSection(SolutionProperties) = preSolution
60 | HideSolutionNode = FALSE
61 | EndGlobalSection
62 | GlobalSection(ExtensibilityGlobals) = postSolution
63 | SolutionGuid = {F0B1EA06-0948-4022-AE82-C7425F3C9C5A}
64 | EndGlobalSection
65 | EndGlobal
66 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/ClientApp/src/components/error-page.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) Microsoft. All rights reserved.
4 |
5 | */
6 |
7 | import * as React from "react";
8 | import { Text, Flex, Icon, Provider, themes } from "@fluentui/react";
9 | import * as microsoftTeams from "@microsoft/teams-js";
10 | import { getResourceStrings } from "../api/remote-support-api";
11 |
12 | interface errorPageState {
13 | theme: string | "",
14 | themeStyle: any | "",
15 | resourceStrings: any | "",
16 | }
17 |
18 | const DarkTheme = "dark";
19 | const ContrastTheme = "contrast";
20 |
21 | export default class ErrorPage extends React.Component<{}, errorPageState> {
22 | code: string | null = null;
23 | token: string | null = null;
24 | locale?: string | null;
25 |
26 | constructor(props: any) {
27 | super(props);
28 | let search = window.location.search;
29 | let params = new URLSearchParams(search);
30 | this.token = params.get("token");
31 | this.code = params.get("code");
32 | this.state = {
33 | theme: "",
34 | themeStyle: themes.teams,
35 | resourceStrings: {}
36 | };
37 | }
38 |
39 | /** Called once component is mounted. */
40 | async componentDidMount() {
41 | microsoftTeams.initialize();
42 | microsoftTeams.getContext((context) => {
43 | let theme = context.theme || "";
44 | this.updateTheme(theme);
45 | this.locale = context.locale;
46 | this.setState({
47 | theme: theme
48 | });
49 | this.getResourceStrings();
50 | });
51 |
52 | microsoftTeams.registerOnThemeChangeHandler((theme) => {
53 | this.updateTheme(theme);
54 | this.setState({
55 | theme: theme,
56 | }, () => {
57 | this.forceUpdate();
58 | });
59 | });
60 | }
61 |
62 | /**
63 | * Set current theme state received from teams context
64 | * @param {String} theme Current theme name
65 | */
66 | private updateTheme = (theme: string) => {
67 | if (theme === DarkTheme) {
68 | this.setState({
69 | themeStyle: themes.teamsDark
70 | });
71 | } else if (theme === ContrastTheme) {
72 | this.setState({
73 | themeStyle: themes.teamsHighContrast
74 | });
75 | } else {
76 | this.setState({
77 | themeStyle: themes.teams
78 | });
79 | }
80 | }
81 |
82 | /**
83 | * Get resource strings according to user locale.
84 | * */
85 | getResourceStrings = async () => {
86 | const resourceStringsResponse = await getResourceStrings(this.token!, this.locale);
87 |
88 | if (resourceStringsResponse.status === 200) {
89 | this.setState({ resourceStrings: resourceStringsResponse.data });
90 | }
91 | }
92 |
93 | render() {
94 | let message = this.state.resourceStrings.errorMessage;
95 | if (this.code === "401") {
96 | message = `${this.state.resourceStrings.unauthorizedAccess}`;
97 | }
98 | return (
99 |
100 |
101 |
102 |
103 |
104 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | );
125 | }
126 | }
--------------------------------------------------------------------------------
/Manifest/SME/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
3 | "manifestVersion": "1.5",
4 | "version": "1.0.0",
5 | "id": "78f1b2b2-d7d8-4e10-ba68-7ad587d3a56e",
6 | "packageName": "com.microsoft.teams.remotesupport.experts",
7 | "developer": {
8 | "name": "<>",
9 | "websiteUrl": "<>",
10 | "privacyUrl": "<>",
11 | "termsOfUseUrl": "<>"
12 | },
13 | "localizationInfo": {
14 | "defaultLanguageTag": "en",
15 | "additionalLanguages": [
16 | {
17 | "languageTag": "de",
18 | "file": "de.json"
19 | },
20 | {
21 | "languageTag": "en",
22 | "file": "en.json"
23 | },
24 | {
25 | "languageTag": "fr",
26 | "file": "fr.json"
27 | },
28 | {
29 | "languageTag": "ar",
30 | "file": "ar.json"
31 | },
32 | {
33 | "languageTag": "ja",
34 | "file": "ja.json"
35 | },
36 | {
37 | "languageTag": "es",
38 | "file": "es.json"
39 | },
40 | {
41 | "languageTag": "he",
42 | "file": "he.json"
43 | },
44 | {
45 | "languageTag": "ko",
46 | "file": "ko.json"
47 | },
48 | {
49 | "languageTag": "pt-BR",
50 | "file": "pt-BR.json"
51 | },
52 | {
53 | "languageTag": "ru",
54 | "file": "ru.json"
55 | },
56 | {
57 | "languageTag": "zh-CN",
58 | "file": "zh-CN.json"
59 | },
60 | {
61 | "languageTag": "zh-TW",
62 | "file": "zh-TW.json"
63 | }
64 | ]
65 | },
66 | "icons": {
67 | "color": "color.png",
68 | "outline": "outline.png"
69 | },
70 | "name": {
71 | "short": "Remote Support - Expert"
72 | },
73 | "description": {
74 | "short": "Expert team bot to handle user requests and set on call experts.",
75 | "full": "Manage support request and set up on call experts in team. Search urgent, assigned and unassigned requests right from expert team."
76 | },
77 | "accentColor": "#64A2CC",
78 | "bots": [
79 | {
80 | "botId": "<>",
81 | "scopes": [
82 | "team"
83 | ],
84 | "commandLists": [
85 | {
86 | "scopes": [
87 | "team"
88 | ],
89 | "commands": [
90 | {
91 | "title": "Expert list",
92 | "description": "List of on call experts"
93 | }
94 | ]
95 | }
96 | ],
97 | "supportsFiles": false,
98 | "isNotificationOnly": false
99 | }
100 | ],
101 | "composeExtensions": [
102 | {
103 | "botId": "<>",
104 | "canUpdateConfiguration": false,
105 | "commands": [
106 | {
107 | "id": "urgentrequests",
108 | "type": "query",
109 | "title": "Urgent",
110 | "description": "Search urgent requests",
111 | "parameters": [
112 | {
113 | "name": "searchText",
114 | "title": "Search",
115 | "description": "Search requests"
116 | }
117 | ],
118 | "context": [
119 | "compose",
120 | "commandBox"
121 | ],
122 | "initialRun": true
123 | },
124 | {
125 | "id": "assignedrequests",
126 | "type": "query",
127 | "title": "Assigned",
128 | "description": "Search requests assigned to an expert",
129 | "parameters": [
130 | {
131 | "name": "searchText",
132 | "title": "Search",
133 | "description": "Search requests"
134 | }
135 | ],
136 | "context": [
137 | "compose",
138 | "commandBox"
139 | ],
140 | "initialRun": true
141 | },
142 | {
143 | "id": "unassignedrequests",
144 | "type": "query",
145 | "title": "Unassigned",
146 | "description": "Search unassigned requests not yet assigned to an expert",
147 | "parameters": [
148 | {
149 | "name": "searchText",
150 | "title": "Search",
151 | "description": "Search requests"
152 | }
153 | ],
154 | "context": [
155 | "compose",
156 | "commandBox"
157 | ],
158 | "initialRun": true
159 | }
160 | ]
161 | }
162 | ],
163 | "permissions": [
164 | "identity",
165 | "messageTeamMembers"
166 | ],
167 | "validDomains": [
168 | "<>"
169 | ]
170 | }
--------------------------------------------------------------------------------
/Source/RemoteSupport/Cards/WithdrawCard.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Cards
6 | {
7 | using System.Collections.Generic;
8 | using AdaptiveCards;
9 | using Microsoft.Bot.Schema;
10 | using Microsoft.Extensions.Localization;
11 | using Microsoft.Teams.Apps.RemoteSupport.Common;
12 | using Microsoft.Teams.Apps.RemoteSupport.Models;
13 |
14 | ///
15 | /// Class holds card with confirmation card and withdraw details.
16 | ///
17 | public static class WithdrawCard
18 | {
19 | ///
20 | /// Get the withdraw card with new request button.
21 | ///
22 | /// ticketId of the user.
23 | /// The current cultures' string localizer.
24 | /// Returns an attachment withdraw card.
25 | public static Attachment GetCard(string requestNumber, IStringLocalizer localizer)
26 | {
27 | AdaptiveCard welcomeCard = new AdaptiveCard(new AdaptiveSchemaVersion(Constants.AdaptiveCardVersion))
28 | {
29 | Body = new List
30 | {
31 | new AdaptiveTextBlock
32 | {
33 | HorizontalAlignment = AdaptiveHorizontalAlignment.Left,
34 | Text = localizer.GetString("WithdrawText", requestNumber),
35 | Wrap = true,
36 | },
37 | },
38 | Actions = new List
39 | {
40 | new AdaptiveSubmitAction
41 | {
42 | Title = localizer.GetString("NewRequestButtonText"),
43 | Data = new AdaptiveCardAction
44 | {
45 | MsteamsCardAction = new CardAction
46 | {
47 | Type = Constants.MessageBackActionType,
48 | Text = localizer.GetString("NewRequestButtonText"),
49 | },
50 | },
51 | },
52 | },
53 | };
54 |
55 | return new Attachment
56 | {
57 | ContentType = AdaptiveCard.ContentType,
58 | Content = welcomeCard,
59 | };
60 | }
61 |
62 | ///
63 | /// Card to show confirmation on selecting withdraw action.
64 | ///
65 | /// TicketId of particular request.
66 | /// The current cultures' string localizer.
67 | /// An attachment with confirmation(yes/no)card.
68 | public static AdaptiveCard ConfirmationCard(string ticketId, IStringLocalizer localizer)
69 | {
70 | AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(Constants.AdaptiveCardVersion));
71 | var container = new AdaptiveContainer()
72 | {
73 | Items = new List
74 | {
75 | new AdaptiveTextBlock
76 | {
77 | Text = localizer.GetString("WithdrawConfirmation"),
78 | Wrap = true,
79 | },
80 | },
81 | };
82 | card.Body.Add(container);
83 |
84 | card.Actions.Add(
85 | new AdaptiveSubmitAction()
86 | {
87 | Title = localizer.GetString("Yes"),
88 | Data = new AdaptiveCardAction
89 | {
90 | MsteamsCardAction = new CardAction
91 | {
92 | Type = ActionTypes.MessageBack,
93 | Text = Constants.WithdrawRequestAction,
94 | },
95 | PostedValues = ticketId,
96 | },
97 | });
98 |
99 | card.Actions.Add(
100 | new AdaptiveSubmitAction()
101 | {
102 | Title = localizer.GetString("No"),
103 | Data = new AdaptiveCardAction
104 | {
105 | MsteamsCardAction = new CardAction
106 | {
107 | Type = ActionTypes.MessageBack,
108 | Text = Constants.NoCommand,
109 | },
110 | },
111 | });
112 |
113 | return card;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Bot/RemoteSupportActivityMiddleWare.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Bot
6 | {
7 | using System;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Microsoft.ApplicationInsights.DataContracts;
11 | using Microsoft.Bot.Builder;
12 | using Microsoft.Bot.Schema;
13 | using Microsoft.Extensions.Localization;
14 | using Microsoft.Extensions.Logging;
15 | using Microsoft.Extensions.Options;
16 |
17 | ///
18 | /// Represents middle ware that can operate on incoming activities.
19 | ///
20 | public class RemotesupportActivityMiddleWare : IMiddleware
21 | {
22 | ///
23 | /// Represents unique id of a Tenant.
24 | ///
25 | private readonly string tenantId;
26 |
27 | ///
28 | /// The current cultures' string localizer.
29 | ///
30 | private readonly IStringLocalizer localizer;
31 |
32 | ///
33 | /// Represents a set of key/value application configuration properties for Remote Support bot.
34 | ///
35 | private readonly IOptions options;
36 |
37 | ///
38 | /// Sends logs to the Application Insights service.
39 | ///
40 | private readonly ILogger logger;
41 |
42 | ///
43 | /// Initializes a new instance of the class.
44 | ///
45 | /// A set of key/value application configuration properties.
46 | /// Sends logs to the Application Insights service.
47 | /// The current cultures' string localizer.
48 | public RemotesupportActivityMiddleWare(IOptions options, ILogger logger, IStringLocalizer localizer)
49 | {
50 | this.options = options ?? throw new ArgumentNullException(nameof(options));
51 | this.logger = logger;
52 | this.localizer = localizer;
53 | this.tenantId = this.options.Value.TenantId;
54 | }
55 |
56 | ///
57 | /// Processes an incoming activity in middleware.
58 | ///
59 | /// The context object for this turn.
60 | /// The delegate to call to continue the bot middleware pipeline.
61 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
62 | /// A task that represents the work queued to execute.
63 | ///
64 | /// Middleware calls the next delegate to pass control to the next middleware in
65 | /// the pipeline. If middleware doesn’t call the next delegate, the adapter does
66 | /// not call any of the subsequent middleware’s request handlers or the bot’s receive
67 | /// handler, and the pipeline short circuits.
68 | /// The turnContext provides information about the incoming activity, and other data
69 | /// needed to process the activity.
70 | ///
71 | public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
72 | {
73 | turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext));
74 | next = next ?? throw new ArgumentNullException(nameof(next));
75 | if (turnContext.Activity.Type == ActivityTypes.Message && !this.IsActivityFromExpectedTenant(turnContext))
76 | {
77 | this.logger.LogInformation($"Unexpected tenant Id {turnContext.Activity.Conversation.TenantId}", SeverityLevel.Warning);
78 | await turnContext.SendActivityAsync(activity: MessageFactory.Text(this.localizer.GetString("InvalidTenantText")));
79 | }
80 | else
81 | {
82 | await next(cancellationToken);
83 | }
84 | }
85 |
86 | ///
87 | /// Verify if the tenant Id in the message is the same tenant Id used when application was configured.
88 | ///
89 | /// Context object containing information cached for a single turn of conversation with a user.
90 | /// True if context is from expected tenant else false.
91 | private bool IsActivityFromExpectedTenant(ITurnContext turnContext)
92 | {
93 | return turnContext.Activity.Conversation.TenantId == this.tenantId;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Startup.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport
6 | {
7 | using System;
8 | using Microsoft.ApplicationInsights;
9 | using Microsoft.AspNetCore.Builder;
10 | using Microsoft.AspNetCore.Hosting;
11 | using Microsoft.AspNetCore.Mvc;
12 | using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
13 | using Microsoft.Bot.Builder;
14 | using Microsoft.Bot.Builder.BotFramework;
15 | using Microsoft.Bot.Builder.Integration.AspNet.Core;
16 | using Microsoft.Bot.Connector.Authentication;
17 | using Microsoft.Extensions.Configuration;
18 | using Microsoft.Extensions.DependencyInjection;
19 | using Microsoft.Teams.Apps.RemoteSupport.Bot;
20 |
21 | ///
22 | /// The Startup class is responsible for configuring the DI container and acts as the composition root.
23 | ///
24 | public sealed class Startup
25 | {
26 | private readonly IConfiguration configuration;
27 |
28 | ///
29 | /// Initializes a new instance of the class.
30 | ///
31 | /// The environment provided configuration.
32 | public Startup(IConfiguration configuration)
33 | {
34 | this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
35 | }
36 |
37 | ///
38 | /// Gets Configurations Interfaces.
39 | ///
40 | public IConfiguration Configuration { get; }
41 |
42 | ///
43 | /// Configure the composition root for the application.
44 | ///
45 | /// The stub composition root.
46 | ///
47 | /// For more information see: https://go.microsoft.com/fwlink/?LinkID=398940.
48 | ///
49 | public void ConfigureServices(IServiceCollection services)
50 | {
51 | services.AddHttpContextAccessor();
52 | services.AddCredentialProviders(this.configuration);
53 | services.AddConfigurationSettings(this.configuration);
54 | services.AddProviders();
55 | services.AddCustomJWTAuthentication(this.configuration);
56 |
57 | // In production, the React files will be served from this directory
58 | services.AddSpaStaticFiles(configuration =>
59 | {
60 | configuration.RootPath = "ClientApp/build";
61 | });
62 |
63 | services.AddSingleton();
64 |
65 | // Create the Bot Framework Adapter with error handling enabled.
66 | services.AddSingleton();
67 |
68 | // Create the Bot Framework Adapter with error handling enabled.
69 | services
70 | .AddTransient();
71 | services
72 | .AddTransient();
73 |
74 | // Create the Translation Middleware that will be added to the middleware pipeline in the AdapterWithErrorHandler
75 | services.AddSingleton();
76 | services
77 | .AddTransient(serviceProvider => (BotFrameworkAdapter)serviceProvider.GetRequiredService());
78 | services
79 | .AddMvc()
80 | .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
81 |
82 | // Add i18n.
83 | services.AddLocalizationSettings(this.configuration);
84 | }
85 |
86 | ///
87 | /// Configure the application request pipeline.
88 | ///
89 | /// The application.
90 | /// Hosting environment.
91 | public void Configure(IApplicationBuilder app, IHostingEnvironment environment)
92 | {
93 | if (environment.IsDevelopment())
94 | {
95 | app.UseDeveloperExceptionPage();
96 | }
97 | else
98 | {
99 | app.UseHsts();
100 | }
101 |
102 | app.UseAuthentication();
103 | app.UseRequestLocalization();
104 | app.UseStaticFiles();
105 | app.UseSpaStaticFiles();
106 | app.UseMvc();
107 | app.UseSpa(spa =>
108 | {
109 | spa.Options.SourcePath = "ClientApp";
110 |
111 | if (environment.IsDevelopment())
112 | {
113 | spa.UseReactDevelopmentServer(npmScript: "start");
114 | }
115 | });
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Source/RemoteSupport.Configuration/Microsoft.Teams.Apps.RemoteSupport.Configuration.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 | d3daa5ef-9b6e-4f84-a74d-d37dcce5048e
6 | latest
7 | ClientApp\
8 | true
9 | Latest
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | all
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Always
42 |
43 |
44 |
45 |
46 | $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), .gitignore))\Build\Analyzer.ruleset
47 | bin\$(Configuration)\$(Platform)\$(AssemblyName).xml
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | True
63 | True
64 | Strings.resx
65 |
66 |
67 |
68 |
69 |
70 | Strings.Designer.cs
71 | PublicResXFileCodeGenerator
72 | Microsoft.Teams.Apps.RemoteSupport.Configuration
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | %(DistFiles.Identity)
99 | PreserveNewest
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Cards/WelcomeTeamCard.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Cards
6 | {
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Globalization;
10 | using AdaptiveCards;
11 | using Microsoft.Bot.Schema;
12 | using Microsoft.Extensions.Localization;
13 | using Microsoft.Teams.Apps.RemoteSupport.Common;
14 | using Microsoft.Teams.Apps.RemoteSupport.Models;
15 |
16 | ///
17 | /// This class process Welcome Card when installed in Team scope.
18 | ///
19 | public static class WelcomeTeamCard
20 | {
21 | ///
22 | /// This method will construct the user welcome card when bot is added in team scope.
23 | ///
24 | /// Application base URL.
25 | /// The current cultures' string localizer.
26 | /// User welcome card.
27 | public static Attachment GetCard(string applicationBasePath, IStringLocalizer localizer)
28 | {
29 | AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(Constants.AdaptiveCardVersion))
30 | {
31 | Body = new List
32 | {
33 | new AdaptiveColumnSet
34 | {
35 | Columns = new List
36 | {
37 | new AdaptiveColumn
38 | {
39 | Width = "1",
40 | Items = new List
41 | {
42 | new AdaptiveImage
43 | {
44 | Url = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}/Artifacts/AppIcon.png", applicationBasePath?.Trim('/'))),
45 | Size = AdaptiveImageSize.Large,
46 | },
47 | },
48 | },
49 | new AdaptiveColumn
50 | {
51 | Width = "5",
52 | Items = new List
53 | {
54 | new AdaptiveTextBlock
55 | {
56 | Text = localizer.GetString("WelcomeCardTitle"),
57 | Weight = AdaptiveTextWeight.Bolder,
58 | Size = AdaptiveTextSize.Large,
59 | },
60 | new AdaptiveTextBlock
61 | {
62 | Text = localizer.GetString("WelcomeTeamCardContent"),
63 | Wrap = true,
64 | Spacing = AdaptiveSpacing.None,
65 | },
66 | },
67 | },
68 | },
69 | },
70 | new AdaptiveTextBlock
71 | {
72 | HorizontalAlignment = AdaptiveHorizontalAlignment.Left,
73 | Text = localizer.GetString("WelcomeSubHeaderText"),
74 | Spacing = AdaptiveSpacing.Small,
75 | },
76 | new AdaptiveTextBlock
77 | {
78 | Text = localizer.GetString("TeamRequestBulletPoint"),
79 | Spacing = AdaptiveSpacing.None,
80 | Wrap = true,
81 | },
82 | new AdaptiveTextBlock
83 | {
84 | Text = localizer.GetString("ExperListBulletPoint"),
85 | Spacing = AdaptiveSpacing.None,
86 | Wrap = true,
87 | },
88 | new AdaptiveTextBlock
89 | {
90 | Text = localizer.GetString("ContentText"),
91 | Spacing = AdaptiveSpacing.Small,
92 | },
93 | },
94 | Actions = new List
95 | {
96 | new AdaptiveSubmitAction
97 | {
98 | Title = localizer.GetString("ExpertListTitle"),
99 | Data = new AdaptiveCardAction
100 | {
101 | MsteamsCardAction = new CardAction
102 | {
103 | Type = Constants.MessageBackActionType,
104 | Text = localizer.GetString("ExpertList"),
105 | },
106 | },
107 | },
108 | },
109 | };
110 | return new Attachment
111 | {
112 | ContentType = AdaptiveCard.ContentType,
113 | Content = card,
114 | };
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Source/RemoteSupport/Cards/WelcomeCard.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) Microsoft. All rights reserved.
3 | //
4 |
5 | namespace Microsoft.Teams.Apps.RemoteSupport.Cards
6 | {
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Globalization;
10 | using AdaptiveCards;
11 | using Microsoft.Bot.Schema;
12 | using Microsoft.Extensions.Localization;
13 | using Microsoft.Teams.Apps.RemoteSupport.Common;
14 | using Microsoft.Teams.Apps.RemoteSupport.Models;
15 |
16 | ///
17 | /// This class process welcome card when installed in personal scope.
18 | ///
19 | public static class WelcomeCard
20 | {
21 | ///
22 | /// This method will construct the user welcome card when bot is added in personal scope.
23 | ///
24 | /// Application base URL.
25 | /// The current cultures' string localizer.
26 | /// User welcome card.
27 | public static Attachment GetCard(string applicationBasePath, IStringLocalizer localizer)
28 | {
29 | AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(Constants.AdaptiveCardVersion))
30 | {
31 | Body = new List
32 | {
33 | new AdaptiveColumnSet
34 | {
35 | Columns = new List
36 | {
37 | new AdaptiveColumn
38 | {
39 | Width = "1",
40 | Items = new List
41 | {
42 | new AdaptiveImage
43 | {
44 | Url = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}/Artifacts/AppIcon.png", applicationBasePath?.Trim('/'))),
45 | Size = AdaptiveImageSize.Large,
46 | },
47 | },
48 | },
49 | new AdaptiveColumn
50 | {
51 | Width = "5",
52 | Items = new List
53 | {
54 | new AdaptiveTextBlock
55 | {
56 | Text = localizer.GetString("WelcomeCardTitle"),
57 | Weight = AdaptiveTextWeight.Bolder,
58 | Size = AdaptiveTextSize.Large,
59 | },
60 | new AdaptiveTextBlock
61 | {
62 | Text = localizer.GetString("WelcomeCardContent"),
63 | Wrap = true,
64 | Spacing = AdaptiveSpacing.None,
65 | },
66 | },
67 | },
68 | },
69 | },
70 | new AdaptiveTextBlock
71 | {
72 | HorizontalAlignment = AdaptiveHorizontalAlignment.Left,
73 | Text = localizer.GetString("WelcomeSubHeaderText"),
74 | Spacing = AdaptiveSpacing.Small,
75 | },
76 | new AdaptiveTextBlock
77 | {
78 | Text = localizer.GetString("NewRequestBulletPoint"),
79 | Wrap = true,
80 | Spacing = AdaptiveSpacing.None,
81 | },
82 | new AdaptiveTextBlock
83 | {
84 | Text = localizer.GetString("StatusCheckBulletPoint"),
85 | Wrap = true,
86 | Spacing = AdaptiveSpacing.None,
87 | },
88 | new AdaptiveTextBlock
89 | {
90 | Text = localizer.GetString("ContentText"),
91 | Spacing = AdaptiveSpacing.Small,
92 | },
93 | },
94 |
95 | Actions = new List
96 | {
97 | new AdaptiveSubmitAction
98 | {
99 | Title = localizer.GetString("NewRequestButtonText"),
100 | Data = new AdaptiveCardAction
101 | {
102 | MsteamsCardAction = new CardAction
103 | {
104 | Type = Constants.MessageBackActionType,
105 | Text = localizer.GetString("NewRequestButtonText"),
106 | },
107 | },
108 | },
109 | },
110 | };
111 | return new Attachment
112 | {
113 | ContentType = AdaptiveCard.ContentType,
114 | Content = card,
115 | };
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------