diff --git a/.vscode/settings.json b/.vscode/settings.json index 15189fe..633b995 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,9 @@ -{ - "azureFunctions.deploySubpath": "api", - "azureFunctions.postDeployTask": "npm install", - "azureFunctions.projectLanguage": "JavaScript", - "azureFunctions.projectRuntime": "~3", - "debug.internalConsoleOptions": "neverOpen", - "azureFunctions.preDeployTask": "npm prune" -} \ No newline at end of file +{ + "azureFunctions.deploySubpath": "api", + "azureFunctions.postDeployTask": "npm install", + "azureFunctions.projectLanguage": "JavaScript", + "azureFunctions.projectRuntime": "~3", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.preDeployTask": "npm prune", + "azureFunctions.projectSubpath": "api" +} diff --git a/DEPLOY.md b/DEPLOY.md index 17ccc2b..779b44e 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -19,14 +19,14 @@ 1. Run `npm install` from the VS Code Terminal to install the [node-fetch](https://www.npmjs.com/package/node-fetch) dependency. -1. From the Azure Functions extension within VS Code, click **Deploy to Function App** (cloud icon) and select or create a Function App in Azure. +1. From the Azure Functions extension within VS Code, click **Deploy to Function App** (cloud icon) and select a Function App in Azure. If needed, you can first create a new Azure Function resource within the Azure Portal before deploying from VS Code. -1. From the Azure Function in Azure Portal, click **Configuration** to add the following application settings using either the names & values previously pasted into `/api/local.settings.json` or new values for production resources. +1. From the Azure Function in Azure Portal, click **Configuration** to add the following application settings using either the names & values previously pasted into `/api/local.settings.json` or new values for production resources. Click **Save** to update your Function App. - AadTenantId - AppClientId - AppClientSecret -1. Copy the Function URL from VS Code or Azure Portal (including a key that adds code param) and open in a browser to confirm a successful response from the AAD Token API. If needed, you can troubleshoot issues in the Portal by browsing to the **Functions** and **Code + Test** to debug logs while making an HTTP request. +1. Copy the Function URL from VS Code or Azure Portal (including a key that adds code param) and open in a browser to confirm a successful response from the AAD Token API. If needed, you can troubleshoot issues in the Portal by browsing to the **Functions** then **AadToken** for the **Function Keys** as well as **Code + Test** to debug logs while making an HTTP request. 1. When a token is successfully returned from the API, override the `fetchToken()` reference to `apiUrl` within [src/App/helper.js](./src/App/helper.js#L26) using your new Function URL. It will be in this format: `https://APPNAME.azurewebsites.net/api/aad/token?code=DEFAULT_KEY` @@ -62,3 +62,5 @@ 1. Copy the Static Web App URL from the **Overview** page, then go back to the Azure Function and click **CORS** to paste your app URL in the Allowed Origins. 1. Open the Static Web App URL to confirm it is working the same as the [local deployment method](./README.md). + +1. See [Troubleshooting](./TROUBLESHOOTING.md) doc for more help or [file a new issue](https://github.com/microsoft/Purview-Custom-Types-Tool-Solution-Accelerator/issues). diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..b46afb0 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,10 @@ +# Troubleshooting + +## Debug Azure Deployments + +1. If possible, [run the app locally](https://github.com/microsoft/Purview-Custom-Types-Tool-Solution-Accelerator#local-setup) to confirm your configuration values are correct and your application service principal has the permission to access your Purview account. + +1. Ensure your deployed Function returns a valid token via JSON: + ```https://{AzFuncName}.azurewebsites.net/api/aad/token?code={AzFuncKey}``` + +1. Use the web browser's `More Tools > Developer Tools > Console` (Ctrl + Shift + I) to debug the API calls and responses. diff --git a/api/Purview/purviewHelper.js b/api/Purview/purviewHelper.js index 1681c98..51a1a0f 100644 --- a/api/Purview/purviewHelper.js +++ b/api/Purview/purviewHelper.js @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -const fetch = require("node-fetch"); +// const fetch = require("node-fetch"); const AtlasAccountName = process.env["AtlasAccountName"] || null, apiPrefix = `https://${AtlasAccountName}.catalog.purview.azure.com/api`; // GET all type definitions async function getTypeDefs(token) { + const fetch = (await import('node-fetch')).default; + const apiUrl = (token && `${apiPrefix}/atlas/v2/types/typedefs`) || null, fetchResponse = apiUrl && await fetch(apiUrl, {headers:{'Authorization': `Bearer ${token}`}}); diff --git a/api/Storage/index.js b/api/Storage/index.js index d4a3694..d28dc21 100644 --- a/api/Storage/index.js +++ b/api/Storage/index.js @@ -8,14 +8,16 @@ module.exports = async function (context, req) { const STORAGE_CONNECTION_STRING = process.env["StorageConnectionString"] || null, STORAGE_CONTAINER = process.env["StorageContainer"] || null, blobServiceClient = STORAGE_CONNECTION_STRING - && BlobServiceClient.fromConnectionString(STORAGE_CONNECTION_STRING) || null; - - let contentBody = ''; - let httpStatus = 400; + && BlobServiceClient.fromConnectionString(STORAGE_CONNECTION_STRING) || null, + blobContainers = (blobServiceClient && await blobServiceClient.listContainers()) || null; + + let contentBody = { version: "v1.1" }, + httpStatus = 400; context.log(`GET all containers looking for '${STORAGE_CONTAINER}'...`); let containerExists = false; - for await (const container of blobServiceClient.listContainers()) { + + for await (const container of blobContainers) { const containerName = (container && container.name) || null; if (containerName === STORAGE_CONTAINER) { containerExists = true; diff --git a/api/package.json b/api/package.json index 10f08ee..759d57f 100644 --- a/api/package.json +++ b/api/package.json @@ -6,6 +6,9 @@ "start": "func start", "test": "echo \"No tests yet...\"" }, - "dependencies": {}, + "dependencies": { + "@azure/storage-blob": "^12.11.0", + "node-fetch": "^3.2.3" + }, "devDependencies": {} } diff --git a/package-lock.json b/package-lock.json index a332216..b3cf062 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3860,9 +3860,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001328", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz", - "integrity": "sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ==" + "version": "1.0.30001414", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz", + "integrity": "sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -9399,7 +9399,7 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", diff --git a/public/index.html b/public/index.html index 13291fa..08bc6d3 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,6 @@ - Purview Custom Types Tool diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index b8ce34c..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "Purview Custom Types Tool", - "name": "Microsoft Purview Custom Types Tool", - "icons": [ - { - "src": "favicon.ico", - "sizes": "128x128 64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/src/App/helper.js b/src/App/helper.js index 64f3ee1..f12bc2f 100644 --- a/src/App/helper.js +++ b/src/App/helper.js @@ -15,7 +15,10 @@ export async function fetchAuth(callback) { if (contentType && contentType.indexOf("application/json") !== -1) { await authResponse.json() - .then(authResponse => callback(authResponse)) + .then(authResponse => { + console.log('### authResponse:', authResponse); + callback(authResponse); + }) .catch(error => console.error('Error:', error)); } } @@ -37,6 +40,8 @@ export async function fetchToken(callback) { tokenResponseData = (tokenResponseStatus===200 && tokenResponse.data) || null, tokenType = (tokenResponseData && tokenResponseData.token_type) || null; + console.log('### tokenResponse:', tokenResponse); + // Set token value if (tokenType==='Bearer' && tokenResponseData.access_token) tokenAccess = tokenResponseData.access_token; }) @@ -52,13 +57,13 @@ export async function fetchContainer(callback) { console.log(`### FETCH: GET ${apiUrl}`); const storageResponse = await fetch(apiUrl).catch(error => console.error('Error:', error)), contentType = storageResponse && storageResponse.headers.get("content-type"); - + if (contentType && contentType.indexOf("application/json") !== -1) { await storageResponse.json() .then(storageResponse => { const responseStatus = storageResponse && storageResponse.status, responseData = storageResponse && storageResponse.data, - container = responseData && responseData.name; + container = responseData && (responseData.name || responseData._containerName); if (responseStatus === 200 && container) { callback(container); console.log('### container:', container); @@ -87,10 +92,11 @@ export async function handleApiTypes(response, callback) { if (contentType && contentType.indexOf("application/json") !== -1) { await response.json() .then(response => { - const responseStatus = response && response.status; - if (responseStatus === 200) { - callback(response.data); - console.log('### typeDefsObj:', response.data); + const responseStatus = (response && response.status) || null, + responseData = (response && response.data) || null; + console.log('### typeDefsObj:', responseData); + if (responseStatus === 200 && responseData) { + callback(responseData); } }) .catch(error => console.error('Error:', error)); diff --git a/src/index.js b/src/index.js index e818e8e..d9baa13 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import App from './App/App'; -ReactDOM.render( +const container = document.getElementById('root'); +const root = createRoot(container); // createRoot(container!) if you use TypeScript + +root.render( - , - document.getElementById('root') + ); diff --git a/staticwebapp.config.json b/staticwebapp.config.json new file mode 100644 index 0000000..9d0ef55 --- /dev/null +++ b/staticwebapp.config.json @@ -0,0 +1,36 @@ +{ + "trailingSlash": "auto", + + "routes": [ + { "route": "/logout", "rewrite": "/.auth/logout" }, + { "route": "/login", "rewrite": "/.auth/login/aad" }, + { "route": "/.auth/login/github", "statusCode": 404 }, + { "route": "/.auth/login/google", "statusCode": 404 }, + { "route": "/.auth/login/facebook", "statusCode": 404 }, + { "route": "/.auth/login/twitter", "statusCode": 404 }, + { + "route": "/*", + "allowedRoles": ["admin"] + } + ], + + "responseOverrides": { + "401": { + "statusCode": 302, + "redirect": "/login" + } + }, + + "navigationFallback": { + "rewrite": "/index.html", + "exclude": ["/assets/*", "/*.{json,txt}"] + }, + + "mimeTypes": { + ".json": "text/json" + }, + + "platform": { + "apiRuntime": "node:14" + } +}