From 14324d47657e06d34e873b8f5e3d53aaa2c84cee Mon Sep 17 00:00:00 2001 From: Natasa Manousopoulou Date: Wed, 4 Sep 2024 10:43:29 +0000 Subject: [PATCH 1/2] Changed aad command to entra in PowerShell setup script --- setup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.ps1 b/setup.ps1 index 9558c90..4cd5430 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -4,7 +4,7 @@ npx -p @pnp/cli-microsoft365 -- m365 login --authType browser # create AAD app Write-Host "Creating AAD app..." -$appInfo=$(npx -p @pnp/cli-microsoft365 -- m365 aad app add --name "Microsoft Graph documentation" --withSecret --apisApplication "https://graph.microsoft.com/ExternalConnection.ReadWrite.OwnedBy, https://graph.microsoft.com/ExternalItem.ReadWrite.OwnedBy" --grantAdminConsent --output json) +$appInfo=$(npx -p @pnp/cli-microsoft365 -- m365 entra app add --name "Microsoft Graph documentation" --withSecret --apisApplication "https://graph.microsoft.com/ExternalConnection.ReadWrite.OwnedBy, https://graph.microsoft.com/ExternalItem.ReadWrite.OwnedBy" --grantAdminConsent --output json) # write app to env.ts Write-Host "Writing app to src/env.ts..." From 82a97a89db92d72d90c0e326267315f0303bf1ab Mon Sep 17 00:00:00 2001 From: Natasa Manousopoulou Date: Thu, 5 Sep 2024 14:34:19 +0300 Subject: [PATCH 2/2] Exercise changes --- .gitignore | 3 +- package-lock.json | 132 ++++++++++++++++++++++++++++++++++++++-- package.json | 3 + setup.ps1 | 2 +- src/config.ts | 15 +++-- src/createConnection.ts | 20 +++--- src/loadContent.ts | 86 +++++++++++++++++++------- 7 files changed, 221 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 17fb15d..697d6f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ env* dist -node_modules \ No newline at end of file +node_modules +content \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 757c6b4..1b3bff7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,24 @@ { - "name": "connector-msgraph-docs", - "version": "1.0.0", + "name": "microsoft-graph-connector", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "connector-msgraph-docs", - "version": "1.0.0", + "name": "microsoft-graph-connector", + "version": "0.1.0", "license": "ISC", "dependencies": { "@azure/identity": "^4.3.0", "@microsoft/microsoft-graph-client": "^3.0.7", + "gray-matter": "^4.0.3", + "remove-markdown": "^0.5.5", "undici": "^6.19.2" }, "devDependencies": { "@microsoft/microsoft-graph-types": "^2.40.0", "@types/node": "^20.14.10", + "@types/remove-markdown": "^0.3.4", "rimraf": "^6.0.1", "typescript": "^5.5.3" } @@ -292,6 +295,13 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/remove-markdown": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.4.tgz", + "integrity": "sha512-i753EH/p02bw7bLlpfS/4CV1rdikbGiLabWyVsAvsFid3cA5RNU1frG7JycgY+NSnFwtoGlElvZVceCytecTDA==", + "dev": true, + "license": "MIT" + }, "node_modules/agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", @@ -327,6 +337,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -423,6 +442,19 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -431,6 +463,18 @@ "node": ">=0.8.x" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -470,6 +514,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -508,6 +567,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -552,6 +620,19 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -611,6 +692,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -736,6 +826,12 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/remove-markdown": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.5.tgz", + "integrity": "sha512-lMR8tOtDqazFT6W2bZidoXwkptMdF3pCxpri0AEokHg0sZlC2GdoLqnoaxsEj1o7/BtXV1MKtT3YviA1t7rW7g==", + "license": "MIT" + }, "node_modules/rimraf": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", @@ -774,6 +870,19 @@ } ] }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -818,6 +927,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", @@ -923,6 +1038,15 @@ "node": ">=8" } }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", diff --git a/package.json b/package.json index 3d19ec9..b7107c4 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,15 @@ "devDependencies": { "@microsoft/microsoft-graph-types": "^2.40.0", "@types/node": "^20.14.10", + "@types/remove-markdown": "^0.3.4", "rimraf": "^6.0.1", "typescript": "^5.5.3" }, "dependencies": { "@azure/identity": "^4.3.0", "@microsoft/microsoft-graph-client": "^3.0.7", + "gray-matter": "^4.0.3", + "remove-markdown": "^0.5.5", "undici": "^6.19.2" } } diff --git a/setup.ps1 b/setup.ps1 index 9558c90..4cd5430 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -4,7 +4,7 @@ npx -p @pnp/cli-microsoft365 -- m365 login --authType browser # create AAD app Write-Host "Creating AAD app..." -$appInfo=$(npx -p @pnp/cli-microsoft365 -- m365 aad app add --name "Microsoft Graph documentation" --withSecret --apisApplication "https://graph.microsoft.com/ExternalConnection.ReadWrite.OwnedBy, https://graph.microsoft.com/ExternalItem.ReadWrite.OwnedBy" --grantAdminConsent --output json) +$appInfo=$(npx -p @pnp/cli-microsoft365 -- m365 entra app add --name "Microsoft Graph documentation" --withSecret --apisApplication "https://graph.microsoft.com/ExternalConnection.ReadWrite.OwnedBy, https://graph.microsoft.com/ExternalItem.ReadWrite.OwnedBy" --grantAdminConsent --output json) # write app to env.ts Write-Host "Writing app to src/env.ts..." diff --git a/src/config.ts b/src/config.ts index 845a08c..e32b06c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,9 +2,9 @@ import { ExternalConnectors } from '@microsoft/microsoft-graph-types'; export const config = { connection: { - id: '', - name: '', - description: '', + id: 'msgraphdocs', + name: 'Microsoft Graph documentation', + description: 'Documentation for Microsoft Graph API which explains what Microsoft Graph is and how to use it.', activitySettings: { // URL to item resolves track activity such as sharing external items. // The recorded activity is used to improve search relevance. @@ -60,7 +60,14 @@ export const config = { labels: [ 'iconUrl' ] - } + }, + { + name: 'description', + type: 'string', + isQueryable: true, + isSearchable: true, + isRetrievable: true + }, ] } } as ExternalConnectors.ExternalConnection diff --git a/src/createConnection.ts b/src/createConnection.ts index 51c8980..a7eaea7 100644 --- a/src/createConnection.ts +++ b/src/createConnection.ts @@ -1,4 +1,4 @@ -import { ExternalConnectors } from '@microsoft/microsoft-graph-types'; +import { ExternalAudienceScope, ExternalConnectors } from '@microsoft/microsoft-graph-types'; import fs from 'fs'; import { config } from './config.js'; import { client } from './graphClient.js'; @@ -14,15 +14,19 @@ async function createConnection(): Promise { const adaptiveCard = fs.readFileSync('./resultLayout.json', 'utf8'); searchSettings!.searchResultTemplates![0].layout = JSON.parse(adaptiveCard); + const requestBody : ExternalConnectors.ExternalConnection = { + id, + name, + description, + activitySettings, + searchSettings + }; + + console.log(requestBody); + await client .api('/external/connections') - .post({ - id, - name, - description, - activitySettings, - searchSettings - }); + .post(requestBody); console.log('Connection created'); } diff --git a/src/loadContent.ts b/src/loadContent.ts index 6a9678c..0f0223f 100644 --- a/src/loadContent.ts +++ b/src/loadContent.ts @@ -2,38 +2,86 @@ import { GraphError } from '@microsoft/microsoft-graph-client'; import { ExternalConnectors } from '@microsoft/microsoft-graph-types'; import { config } from './config.js'; import { client } from './graphClient.js'; +import matter, { GrayMatterFile } from 'gray-matter'; +import fs from 'fs'; +import path from 'path'; +import removeMd from 'remove-markdown'; // Represents the document to import -interface Document { - // Document title - title: string; - // Document content. Can be plain-text or HTML +interface Document extends GrayMatterFile { content: string; - // URL to the document in the external system - url: string; - // URL to the document icon. Required by Microsoft Copilot for Microsoft 365 - iconUrl: string; + relativePath: string; + url?: string; + iconUrl?: string; } function extract(): Document[] { - // return the documents to import + const contentDir = 'content'; + const baseUrl = 'https://learn.microsoft.com/graph/'; + + const content: Document[] = []; + const contentFiles = fs.readdirSync(contentDir, { recursive: true }); + + contentFiles.forEach(file => { + if (!file.toString().endsWith('.md')) { + return; + } + + const fileContents = fs.readFileSync(path.join(contentDir, file.toString()), 'utf-8'); + const doc = matter(fileContents) as Document; + + doc.content = removeMd(doc.content.replace(/<[^>]+>/g, ' ')); + doc.relativePath = file.toString(); + doc.url = new URL(doc.relativePath.replace('.md', ''), baseUrl).toString(); + doc.iconUrl = 'https://raw.githubusercontent.com/waldekmastykarz/img/main/microsoft-graph.png'; + + content.push(doc); + }); + + return content; } function getDocId(doc: Document): string { - // Generate a unique ID for the document. - // ID can't contain / - // Generate an ID that you can resolve back to the document's URL - // so that URL to item resolvers can properly record activity. + const id = doc.relativePath.replace(path.sep, '__').replace('.md', ''); + return id; } function transform(documents: Document[]): ExternalConnectors.ExternalItem[] { return documents.map(doc => { const docId = getDocId(doc); + + let acl = [ + { + accessType: 'grant', + type: 'everyone', + value: 'everyone' + } + ]; + + if (doc.relativePath.endsWith('use-the-api.md')) { + acl = [ + { + accessType: 'grant', + type: 'user', + value: '2e75bd61-7a32-44aa-b8a7-ff051804df25' + }, + ]; + } + else if (doc.relativePath.endsWith('traverse-the-graph.md')) { + acl = [ + { + accessType: 'grant', + type: 'group', + value: 'a9fd282f-4634-4cba-9dd4-631a2ee83cd3', + } + ]; + } + return { id: docId, properties: { - // Add properties as defined in the schema in config.ts - title: doc.title ?? '', + title: doc.data.title ?? '', + description: doc.data.description ?? '', url: doc.url, iconUrl: doc.iconUrl }, @@ -41,13 +89,7 @@ function transform(documents: Document[]): ExternalConnectors.ExternalItem[] { value: doc.content ?? '', type: 'text' }, - acl: [ - { - accessType: 'grant', - type: 'everyone', - value: 'everyone' - } - ] + acl: acl, } as ExternalConnectors.ExternalItem }); }