From 1f4dc3d4a9daf69813f6865274d90433e0943b44 Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Tue, 14 Mar 2023 11:42:29 -0600 Subject: [PATCH 1/8] Notify users when clicking enterprise subscription links --- .gitignore | 3 + package.json | 1 + src/components/.d.ts | 1 + src/components/SubscribersOnlyModal.tsx | 42 +++---- src/theme/Root.js | 145 +++++++++++++++++------- yarn.lock | 4 + 6 files changed, 134 insertions(+), 62 deletions(-) create mode 100644 src/components/.d.ts diff --git a/.gitignore b/.gitignore index 514c5aaa81..355606efe1 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ build/ # IntelliJ files .idea *.iml + +# VS Code files +.vscode diff --git a/package.json b/package.json index 0986a1083d..b9fe961fb5 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-modal": "^3.14.4", + "ts-commons": "gruntwork-io/ts-commons#v1.0.0", "ts-jest": "^27.1.3", "url-loader": "^4.1.1" }, diff --git a/src/components/.d.ts b/src/components/.d.ts new file mode 100644 index 0000000000..2ab174fdbc --- /dev/null +++ b/src/components/.d.ts @@ -0,0 +1 @@ +declare module "*.module.css" diff --git a/src/components/SubscribersOnlyModal.tsx b/src/components/SubscribersOnlyModal.tsx index 93e5dbdaa1..fa75e33cd9 100644 --- a/src/components/SubscribersOnlyModal.tsx +++ b/src/components/SubscribersOnlyModal.tsx @@ -2,13 +2,17 @@ import React from "react" import { Modal } from "./Modal" import styles from "./Modal.module.css" +export const gruntworkGithubOrg = "https://github.com/gruntwork-io/" + +/** @type {RegExp} Match a link prefixed by the gruntworkGithubOrg and capture the next path reference */ +export const repoNamePattern = new RegExp(`^${gruntworkGithubOrg}(.*?)(\/|$)`) + interface SubscribersOnlyModalProps { externalLink: string localStorageKey: string subscriberType?: string showModal: boolean - handleCancelRequest: () => void - handleAcceptRequest?: () => void + clearLink: () => void } export const SubscribersOnlyModal: React.FC = ({ @@ -16,8 +20,7 @@ export const SubscribersOnlyModal: React.FC = ({ localStorageKey, subscriberType, showModal, - handleCancelRequest, - handleAcceptRequest, + clearLink, }) => { const onRequestClose = (e) => { // If the user checked to never see this notice but subsequently cancels we will disregard their selection. We will @@ -26,15 +29,18 @@ export const SubscribersOnlyModal: React.FC = ({ window.localStorage.removeItem(localStorageKey) } - handleCancelRequest() + clearLink() + e.preventDefault() // prevent the browser from handling a Cancel button click and scrolling to top + } - // prevent the browser from handling a Cancel button click and scrolling to top - e.preventDefault() + const repoNameMatchArray: RegExpMatchArray | null = + externalLink.match(repoNamePattern) + + if (!repoNameMatchArray) { + return <> // The link is not a Gruntwork Github repo link } - const gitHubRepoName = externalLink.match( - /https:\/\/github.com\/gruntwork-io\/([^/]*)/ - ) + const repoName = repoNameMatchArray[1] const setDontWarnMe = (event) => { event.stopPropagation() @@ -51,8 +57,8 @@ export const SubscribersOnlyModal: React.FC = ({ shouldCloseOnEsc={true} shouldAcceptOnEnter={false} shouldCloseOnOverlayClick={true} - handleCancelRequest={handleCancelRequest} - handleAcceptRequest={handleAcceptRequest} + handleCancelRequest={clearLink} + handleAcceptRequest={clearLink} >

{subscriberType @@ -60,18 +66,16 @@ export const SubscribersOnlyModal: React.FC = ({ : "For Subscribers Only"}

- This link leads to the private{" "} - {gitHubRepoName && gitHubRepoName.length >= 1 && ( - {gitHubRepoName[1]} - )}{" "} - repository visible only to subscribers; everyone else will see a 404. + This link leads to the private {repoName} repository + visible only to {subscriberType && `${subscriberType} `} + subscribers; everyone else will see a 404.

- onRequestClose(e)} href="#"> + Cancel = ({ target="_blank" data-modal-exempt={true} onClick={() => { - handleAcceptRequest() + setTimeout(clearLink, 500) // Wait .5seconds to allow propagation to external link before clearing the link from state }} > Continue to GitHub diff --git a/src/theme/Root.js b/src/theme/Root.js index 3aebc477ad..f4a18b38ea 100644 --- a/src/theme/Root.js +++ b/src/theme/Root.js @@ -1,9 +1,20 @@ -import React, { useState, useEffect } from "react" -import { SubscribersOnlyModal } from "/src/components/SubscribersOnlyModal.tsx" +/** + * This file is the mechanism for adding stateful logic to a docusaurus + * application since the component is rendered at the very top of the + * React tree and never unmounts. + * We swizzle(customize) the Root component by creating this file: Root.js + * https://docusaurus.io/docs/swizzling#wrapper-your-site-with-root + */ -const gruntworkGithubOrg = "https://github.com/gruntwork-io/" +import React, { useState, useEffect } from "react" +import { enterpriseRepos, awsCISRepos } from "ts-commons/lib/repo-sets" +import { + SubscribersOnlyModal, + repoNamePattern, + gruntworkGithubOrg, +} from "/src/components/SubscribersOnlyModal.tsx" -const gruntworkCisRepoName = "terraform-aws-cis-service-catalog" +console.log("awsCISRepos", awsCISRepos) const publicGruntworkRepoNames = [ "bash-commons", @@ -54,28 +65,43 @@ const publicGruntworkRepoNames = [ ] /** - * Checks if a link references a known public Gruntwork repo + * Checks if a given list of repo names includes a repo name extracted from a given url that matches the repoNamePattern * - * @param string url + * @param {string[]} repoNames + * @param {string} url * @return {boolean} */ -const isPublicGruntworkRepo = (url) => { +const listIncludesRepo = (repoNames, url) => { if (!url) { return false } - // Match a link prefixed by the gruntworkGithubOrg and capture the next path reference - const pattern = new RegExp(`^${gruntworkGithubOrg}(.*?)(\/|$)`) - // e.g for a given link https://github.com/gruntwork-io/docs/intro -> `docs` - const repoName = url.match(pattern)[1] + const repoMatchArray = url.match(repoNamePattern) + if (!repoMatchArray) { + return false + } + + const repoName = repoMatchArray[1] // e.g for a given link https://github.com/gruntwork-io/docs/intro -> `docs` + + // returns boolean + return repoNames.includes(repoName) +} + +/** + * Checks if a link references a known public Gruntwork repo + * + * @param {string} url + * @return {boolean} + */ +const isPublicGruntworkRepo = (url) => { // returns boolean - return publicGruntworkRepoNames.includes(repoName) + return listIncludesRepo(publicGruntworkRepoNames, url) } /** * Checks if a link references a private Gruntwork repo * - * @param string url + * @param {string} url * @return {boolean} */ const isPrivateGruntworkRepo = (url) => { @@ -85,33 +111,55 @@ const isPrivateGruntworkRepo = (url) => { } /** - * Checks if a link references the Gruntwork CIS service catalog repo + * Checks if a link references a Gruntwork CIS repo * - * @param string url + * @param {string} url * @return {boolean} */ - const isGruntworkCisRepo = (url) => { - return url && url.startsWith(`${gruntworkGithubOrg}${gruntworkCisRepoName}`) + // awsCISRepos is an array of strings, e.g. `gruntwork-io/cis-docs` + const cisRepoNames = awsCISRepos.map((repo) => repo.split("/")[1]) + return listIncludesRepo(cisRepoNames, url) +} + +/** + * Checks if a link references a Gruntwork Enterprise repo + * + * @param {string} url + * @return {boolean} + */ +const isGruntworkEnterpriseRepo = (url) => { + // enterpriseRepos is an array of strings, e.g. `gruntwork-io/enterprise-docs` + const enterpriseRepoNames = enterpriseRepos.map((repo) => repo.split("/")[1]) + return listIncludesRepo(enterpriseRepoNames, url) } export const DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY = "dontWarnGitHubLinks" export const DONT_SHOW_CIS_GITHUB_WARNING_KEY = "dontWarnCISLinks" +export const DONT_SHOW_ENTERPRISE_GITHUB_WARNING_KEY = "dontWarnEnterpriseLinks" function Root({ children }) { - const [displaySubscriberNotice, setDisplaySubscriberNotice] = useState(false) const [subscriberNoticeLink, setSubscriberNoticeLink] = useState("") - - const [displayCisNotice, setDisplayCisNotice] = useState(false) const [cisNoticeLink, setCisNoticeLink] = useState("") + const [enterpriseNoticeLink, setEnterpriseNoticeLink] = useState("") - useEffect(() => { + useEffect(function showModalForPrivateGithubLinks() { + console.log("useEffect showModalForPrivateGithubLinks") const listener = (event) => { + console.log("Event Registered repo") + // debugger // Sometimes our links wrap components, such as Cards. In these cases, the event // target is often a child element of the we're attempting to extract the // href data from, and so we search for the closest parent . In the event that // an is clicked directly, that itself will be returned. - const targetLink = event.target.closest("a") + const targetLink = event?.target?.closest("a") + + if (!targetLink || !targetLink.href) { + return + } + + console.log("HREF", targetLink.href) + // debugger // Allow clicks on the external GitHub link FROM the modal notices to work normally if (targetLink.dataset.modalExempt) { @@ -119,22 +167,39 @@ function Root({ children }) { } if (isGruntworkCisRepo(targetLink.href)) { + console.log("CIS repo") const dontWarn = window.localStorage.getItem( DONT_SHOW_CIS_GITHUB_WARNING_KEY ) if (dontWarn) { - setDisplayCisNotice(false) return } - event.preventDefault() + event.preventDefault() // This prevents the link from opening & ensures the modal is displayed setCisNoticeLink(targetLink.href) - setDisplayCisNotice(true) + return + } + + if (isGruntworkEnterpriseRepo(targetLink.href)) { + console.log("Enterprise repo") + + const dontWarn = window.localStorage.getItem( + DONT_SHOW_ENTERPRISE_GITHUB_WARNING_KEY + ) + + if (dontWarn) { + return + } + + event.preventDefault() // This prevents the link from opening & ensures the modal is displayed + setEnterpriseNoticeLink(targetLink.href) return } if (isPrivateGruntworkRepo(targetLink.href)) { + console.log("Private repo") + const dontWarn = window.localStorage.getItem( DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY ) @@ -144,9 +209,8 @@ function Root({ children }) { return } - event.preventDefault() + event.preventDefault() // This prevents the link from opening & ensures the modal is displayed setSubscriberNoticeLink(targetLink.href) - setDisplaySubscriberNotice(true) return } } @@ -160,29 +224,24 @@ function Root({ children }) { return ( <> { - setDisplaySubscriberNotice(false) - setSubscriberNoticeLink("") - }} - handleAcceptRequest={() => { - setDisplaySubscriberNotice(false) - }} + clearLink={() => setSubscriberNoticeLink("")} /> { - setDisplayCisNotice(false) - setCisNoticeLink("") - }} - handleAcceptRequest={() => { - setDisplayCisNotice(false) - }} + clearLink={() => setCisNoticeLink("")} + /> + setEnterpriseNoticeLink("")} /> {children} diff --git a/yarn.lock b/yarn.lock index 5539e45f5c..62a58ffa93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11185,6 +11185,10 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +ts-commons@gruntwork-io/ts-commons#v1.0.0: + version "1.0.0" + resolved "git+ssh://git@github.com/gruntwork-io/ts-commons.git#1a56fec327ea602b4472663599c63ec6118b7f01" + ts-essentials@^2.0.3: version "2.0.12" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" From e0a6fb877d6a4290d629f479bf89c504718c862e Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Tue, 14 Mar 2023 11:48:55 -0600 Subject: [PATCH 2/8] Remove debug logs --- src/theme/Root.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/theme/Root.js b/src/theme/Root.js index f4a18b38ea..392d79757e 100644 --- a/src/theme/Root.js +++ b/src/theme/Root.js @@ -14,8 +14,6 @@ import { gruntworkGithubOrg, } from "/src/components/SubscribersOnlyModal.tsx" -console.log("awsCISRepos", awsCISRepos) - const publicGruntworkRepoNames = [ "bash-commons", "cloud-nuke", @@ -144,10 +142,7 @@ function Root({ children }) { const [enterpriseNoticeLink, setEnterpriseNoticeLink] = useState("") useEffect(function showModalForPrivateGithubLinks() { - console.log("useEffect showModalForPrivateGithubLinks") const listener = (event) => { - console.log("Event Registered repo") - // debugger // Sometimes our links wrap components, such as Cards. In these cases, the event // target is often a child element of the we're attempting to extract the // href data from, and so we search for the closest parent . In the event that @@ -158,16 +153,12 @@ function Root({ children }) { return } - console.log("HREF", targetLink.href) - // debugger - // Allow clicks on the external GitHub link FROM the modal notices to work normally if (targetLink.dataset.modalExempt) { return } if (isGruntworkCisRepo(targetLink.href)) { - console.log("CIS repo") const dontWarn = window.localStorage.getItem( DONT_SHOW_CIS_GITHUB_WARNING_KEY ) @@ -182,8 +173,6 @@ function Root({ children }) { } if (isGruntworkEnterpriseRepo(targetLink.href)) { - console.log("Enterprise repo") - const dontWarn = window.localStorage.getItem( DONT_SHOW_ENTERPRISE_GITHUB_WARNING_KEY ) @@ -198,8 +187,6 @@ function Root({ children }) { } if (isPrivateGruntworkRepo(targetLink.href)) { - console.log("Private repo") - const dontWarn = window.localStorage.getItem( DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY ) From 320c806df182b124e436f4ec4f63edee66d42775 Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Tue, 14 Mar 2023 13:04:24 -0600 Subject: [PATCH 3/8] Add netlify.toml file --- netlify.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 netlify.toml diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000000..3f29b5d5a7 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,2 @@ +[build] + skipYarn = true From 474e0a374fa1e7ed2f459a63c7ef83f15cf256ed Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Tue, 14 Mar 2023 13:34:17 -0600 Subject: [PATCH 4/8] Make ts-commons optional dependency --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b9fe961fb5..dded264830 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-modal": "^3.14.4", - "ts-commons": "gruntwork-io/ts-commons#v1.0.0", "ts-jest": "^27.1.3", "url-loader": "^4.1.1" }, @@ -54,7 +53,8 @@ "yargs": "^17.4.0" }, "optionalDependencies": { - "docs-sourcer": "git+ssh://git@github.com/gruntwork-io/docs-sourcer.git#v0.2" + "docs-sourcer": "git+ssh://git@github.com/gruntwork-io/docs-sourcer.git#v0.2", + "ts-commons": "gruntwork-io/ts-commons#v1.0.0" }, "browserslist": { "production": [ From 6971249f1f71fb92ef561413015fd4a27f9b9ecc Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Wed, 15 Mar 2023 11:34:46 -0600 Subject: [PATCH 5/8] Optionally use ts-commons --- src/theme/Root.js | 4 +++- utils/index.ts | 1 + utils/ts-commons.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 utils/index.ts create mode 100644 utils/ts-commons.ts diff --git a/src/theme/Root.js b/src/theme/Root.js index 392d79757e..a0f5a23192 100644 --- a/src/theme/Root.js +++ b/src/theme/Root.js @@ -7,7 +7,7 @@ */ import React, { useState, useEffect } from "react" -import { enterpriseRepos, awsCISRepos } from "ts-commons/lib/repo-sets" +import { getRepos } from "utils" import { SubscribersOnlyModal, repoNamePattern, @@ -62,6 +62,8 @@ const publicGruntworkRepoNames = [ "website-comments", ] +const { awsCISRepos, enterpriseRepos } = getRepos() + /** * Checks if a given list of repo names includes a repo name extracted from a given url that matches the repoNamePattern * diff --git a/utils/index.ts b/utils/index.ts new file mode 100644 index 0000000000..55e83f435a --- /dev/null +++ b/utils/index.ts @@ -0,0 +1 @@ +export * from "./ts-commons" diff --git a/utils/ts-commons.ts b/utils/ts-commons.ts new file mode 100644 index 0000000000..43e7b166cc --- /dev/null +++ b/utils/ts-commons.ts @@ -0,0 +1,41 @@ +/** + * Check if ts-commons package is available. This is a private package so some + * users may not have access to it. Use this function to check if it is + * available before usage to avoid errors. + * + * @return {*} {Boolean} + */ +const isTSCommonsAvailable = (): Boolean => { + try { + require("ts-commons") + return true + } catch (e) { + return false + } +} + +/** + * Get repos from ts-commons package if available. Otherwise return empty arrays. + * + * @return {*} {{ + * awsCISRepos: string[] + * enterpriseRepos: string[] + * }} + */ +export const getRepos = (): { + awsCISRepos: string[] + enterpriseRepos: string[] +} => { + if (isTSCommonsAvailable()) { + const { awsCISRepos, enterpriseRepos } = require("ts-commons/lib/repo-sets") + return { + awsCISRepos, + enterpriseRepos, + } + } + + return { + awsCISRepos: [], + enterpriseRepos: [], + } +} From 6e9d26154c31993fb4e64ee2ff36459ed51c4c89 Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Wed, 15 Mar 2023 13:47:59 -0600 Subject: [PATCH 6/8] Update ts-commons dynamic import --- src/theme/Root.js | 2 +- utils/ts-commons.ts | 32 +++++++++++--------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/theme/Root.js b/src/theme/Root.js index a0f5a23192..4b7d0911d7 100644 --- a/src/theme/Root.js +++ b/src/theme/Root.js @@ -7,7 +7,7 @@ */ import React, { useState, useEffect } from "react" -import { getRepos } from "utils" +import { getRepos } from "/utils" import { SubscribersOnlyModal, repoNamePattern, diff --git a/utils/ts-commons.ts b/utils/ts-commons.ts index 43e7b166cc..36e3ffbd24 100644 --- a/utils/ts-commons.ts +++ b/utils/ts-commons.ts @@ -1,22 +1,10 @@ -/** - * Check if ts-commons package is available. This is a private package so some - * users may not have access to it. Use this function to check if it is - * available before usage to avoid errors. - * - * @return {*} {Boolean} - */ -const isTSCommonsAvailable = (): Boolean => { - try { - require("ts-commons") - return true - } catch (e) { - return false - } -} - /** * Get repos from ts-commons package if available. Otherwise return empty arrays. * + * ts-commons package is a private package so some users may not have access to + * it. Use this function to check if it is available before usage to avoid + * errors. + * * @return {*} {{ * awsCISRepos: string[] * enterpriseRepos: string[] @@ -26,16 +14,18 @@ export const getRepos = (): { awsCISRepos: string[] enterpriseRepos: string[] } => { - if (isTSCommonsAvailable()) { + try { const { awsCISRepos, enterpriseRepos } = require("ts-commons/lib/repo-sets") return { awsCISRepos, enterpriseRepos, } - } + } catch (e) { + console.log("ts-commons package is NOT available...stubbing out repos.") - return { - awsCISRepos: [], - enterpriseRepos: [], + return { + awsCISRepos: [], + enterpriseRepos: [], + } } } From 120c8a88510e73cba78fdc1eb5775cb54b860b24 Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Wed, 15 Mar 2023 15:24:18 -0600 Subject: [PATCH 7/8] Remove netflify.toml file --- netlify.toml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 netlify.toml diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 3f29b5d5a7..0000000000 --- a/netlify.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] - skipYarn = true From 2441e266ed570e016c981e1f678ad2bf355cf797 Mon Sep 17 00:00:00 2001 From: Oreoluwa Agunbiade Date: Thu, 16 Mar 2023 08:01:16 -0600 Subject: [PATCH 8/8] Update ts-commons util documentation --- utils/ts-commons.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/ts-commons.ts b/utils/ts-commons.ts index 36e3ffbd24..5d35226c60 100644 --- a/utils/ts-commons.ts +++ b/utils/ts-commons.ts @@ -2,8 +2,8 @@ * Get repos from ts-commons package if available. Otherwise return empty arrays. * * ts-commons package is a private package so some users may not have access to - * it. Use this function to check if it is available before usage to avoid - * errors. + * it. Use this function to safely pull in an optional package to avoid both + * build-time and runtime errors. * * @return {*} {{ * awsCISRepos: string[]