diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6983aafa2f8..176c38a7465 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -104,6 +104,7 @@ /src/themes @aws-amplify/documentation-team /src/utils @aws-amplify/documentation-team /tasks @aws-amplify/documentation-team +.github @aws-amplify/documentation-team #Protected Content /src/protected @reesscot @srquinn21 @Milan-Shah @swaminator diff --git a/.github/workflows/check_for_console_errors.yml b/.github/workflows/check_for_console_errors.yml new file mode 100644 index 00000000000..e3852ba97a0 --- /dev/null +++ b/.github/workflows/check_for_console_errors.yml @@ -0,0 +1,38 @@ +name: CheckConsoleErrors +on: + pull_request: + branches: [main] + types: [opened, synchronize] +permissions: + contents: read +jobs: + CheckConsoleErrors: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.2 https://github.com/actions/checkout/commit/b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Setup Node.js 20.x + uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 https://github.com/actions/setup-node/commit/e33196f7422957bea03ed53f6fbb155025ffc7b8 + with: + node-version: 20.x + - name: Install Dependencies + run: yarn + - name: Run Build + run: yarn build:release + env: + NODE_OPTIONS: --max_old_space_size=4096 + - name: Run Server + run: | + python -m http.server 3000 -d ${{ vars.BUILD_DIR }} & + sleep 5 + - name: Run Console Errors + id: consoleErrors + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 https://github.com/actions/github-script/commit/d7906e4ad0b1822421a7e6a35d5ca353c962f410 + with: + result-encoding: string + script: | + const { consoleErrors } = require('./tasks/console-errors.js'); + return await consoleErrors(); + - name: Fail if console errors have been found + if: ${{ steps.consoleErrors.outputs.result }} + run: exit 1 diff --git a/cspell.json b/cspell.json index ae1e2cff180..2f2072f85e9 100644 --- a/cspell.json +++ b/cspell.json @@ -1500,7 +1500,7 @@ "privatesaccess", "menudetaileditors", "editorgroupaccess", - "publicauthreadonly", + "authreadonly", "envs", "Onetoone", "onetomany", diff --git a/public/images/console/7_publicauthreadonly.png b/public/images/console/7_authreadonly.png similarity index 100% rename from public/images/console/7_publicauthreadonly.png rename to public/images/console/7_authreadonly.png diff --git a/src/components/BlockSwitcher/__tests__/BlockSwitcher.test.tsx b/src/components/BlockSwitcher/__tests__/BlockSwitcher.test.tsx index 7c9989f7d91..eae43ec719c 100644 --- a/src/components/BlockSwitcher/__tests__/BlockSwitcher.test.tsx +++ b/src/components/BlockSwitcher/__tests__/BlockSwitcher.test.tsx @@ -21,12 +21,12 @@ describe('BlockSwitcher', () => { const blockSwitcher = await screen.findByText(blockAContent); expect(blockSwitcher).toBeInTheDocument(); }); - + it('should have more than one Block', async () => { render(component); expect(component.props.children.length).toBeGreaterThan(1); }); - + it('should show the first Block as default', async () => { render(component); const tabs = await screen.getAllByRole('tab'); @@ -39,7 +39,7 @@ describe('BlockSwitcher', () => { expect(panels[1]).not.toHaveClass('amplify-tabs__panel--active'); expect(panels[2]).not.toHaveClass('amplify-tabs__panel--active'); }); - + it('should load all Blocks to the DOM', async () => { render(component); const blockA = await screen.findByText(blockAContent); @@ -49,7 +49,7 @@ describe('BlockSwitcher', () => { expect(blockB).toBeInTheDocument(); expect(blockC).toBeInTheDocument(); }); - + it('should switch tabs upon click', async () => { render(component); const tabs = await screen.getAllByRole('tab'); diff --git a/src/pages/[platform]/tools/console/authz/permissions/index.mdx b/src/pages/[platform]/tools/console/authz/permissions/index.mdx index b283883a2fe..81df6e74668 100644 --- a/src/pages/[platform]/tools/console/authz/permissions/index.mdx +++ b/src/pages/[platform]/tools/console/authz/permissions/index.mdx @@ -87,4 +87,4 @@ If you want your data model to be publicly accessible, switch to API_KEY or IAM 1. Using the *Books* data model that you created in the [Create a data model example](/[platform]/tools/console/data/data-model/#Create-a-data-model-example), set the authorization mode to **API Key**. 2. In the **Model** pane on the right, expand the **Anyone** window. Choose **Read** to specify that any signed in user has read access to the data in the *Book* model. -![](/images/console/7_publicauthreadonly.png) +![](/images/console/7_authreadonly.png) diff --git a/tasks/console-errors.js b/tasks/console-errors.js new file mode 100644 index 00000000000..cdc750fe460 --- /dev/null +++ b/tasks/console-errors.js @@ -0,0 +1,109 @@ +const puppeteer = require('puppeteer'); // eslint-disable-line +const { getSitemapUrls } = require('./get-sitemap-links'); // eslint-disable-line + +// Here we are excluding shortbread errors because these are domain specific and are expected to fail in a local environment +const excludedErrors = [ + { + type: 'Shortbread', + errorText: + "Shortbread failed to set user's cookie preference because the domain name that was passed" + } +]; + +const excludedScripts = [ + 'prod.assets.shortbread.aws', + 'prod.tools.shortbread.aws', + 'prod.tools.shortbread.aws.dev', + 'aa0.awsstatic.com', + 'alpha.d2c.marketing.aws.dev', + 'vs-alpha.aws.amazon.com' +]; + +const LOCALHOST = 'http://localhost:3000'; + +const checkPage = async (url) => { + const errorsFound = []; + const browser = await puppeteer.launch({ headless: 'new' }); + + const page = await browser.newPage(); + + await page.setRequestInterception(true); + + page + .on('pageerror', (message) => { + const errorText = message.message; + const excluded = excludedErrors.some((excludedError) => { + return errorText.includes(excludedError.errorText); + }); + + if (!excluded) { + errorsFound.push({ + page: url, + message: errorText + }); + } + }) + .on('console', (message) => { + if (message.type().toLowerCase() === 'error') { + const errorText = message.text(); + const callingScript = message.location().url; + const excludedFromError = excludedErrors.some((excludedError) => { + return errorText.includes(excludedError.errorText); + }); + const excludedFromScript = excludedScripts.some((excludedScript) => { + return callingScript.includes(excludedScript); + }); + const excluded = excludedFromError || excludedFromScript; + + if (!excluded) { + errorsFound.push({ + page: url, + message: errorText, + stackTrace: message.stackTrace() + }); + } + } + }) + + .on('request', (interceptedRequest) => { + const excludedFromScript = excludedScripts.some((excludedScript) => { + return interceptedRequest.url().includes(excludedScript); + }); + if (excludedFromScript) { + interceptedRequest.abort(); + } else interceptedRequest.continue(); + }); + + await page.goto(url, { waitUntil: 'domcontentloaded' }); + + await browser.close(); + + return errorsFound; +}; + +const consoleErrors = async (domain) => { + const pagesToCheck = await getSitemapUrls(domain); + let errorMessage = ''; + for (let i = 0; i < pagesToCheck.length; i++) { + const url = pagesToCheck[i]; + console.log(`checking page ${url}`); + const errorsFound = await checkPage(url); + errorsFound.forEach((error) => { + errorMessage += `${error.message} found on ${error.page}\n`; + }); + } + if (errorMessage != '') { + console.log( + `--------- Console errors have been found and need to be resolved in order to merge. ------- + ------- Please note that these errors could be on pages that were not edited in this PR. ---------` + ); + console.log(errorMessage); + } + return errorMessage; +}; + +module.exports = { + consoleErrors: async (domain = LOCALHOST) => { + return await consoleErrors(domain); + } +}; diff --git a/tasks/get-sitemap-links.js b/tasks/get-sitemap-links.js new file mode 100644 index 00000000000..8e40ac783a2 --- /dev/null +++ b/tasks/get-sitemap-links.js @@ -0,0 +1,42 @@ +const puppeteer = require('puppeteer'); // eslint-disable-line + +const DOMAIN = 'https://docs.amplify.aws'; +const SITEMAP_URL = 'https://docs.amplify.aws/sitemap.xml'; + +const getSitemapUrls = async (localDomain) => { + let browser = await puppeteer.launch({ headless: 'new' }); + + const page = await browser.newPage(); + + let siteMap = localDomain ? `${localDomain}/sitemap.xml` : SITEMAP_URL; + let response = await page.goto(siteMap); + + const siteMapUrls = []; + + if (response && response.status() && response.status() === 200) { + const urlTags = await page.evaluateHandle(() => { + return document.getElementsByTagName('loc'); + }); + const numOfLinks = await page.evaluate((e) => e.length, urlTags); + for (let i = 0; i < numOfLinks; i++) { + let url = await page.evaluate( + (urlTags, i) => urlTags[i].innerHTML, + urlTags, + i + ); + if (localDomain) { + // Currently the sitemap is always generated with the prod docs domain so we need to replace this with localhost + url = url.replace(DOMAIN, localDomain); + } + siteMapUrls.push(url); + } + } + browser.close(); + return siteMapUrls; +}; + +module.exports = { + getSitemapUrls: async (domain) => { + return await getSitemapUrls(domain); + } +};