Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,6 @@ build/
# IntelliJ files
.idea
*.iml

# VS Code files
.vscode
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,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": [
Expand Down
1 change: 1 addition & 0 deletions src/components/.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module "*.module.css"
42 changes: 23 additions & 19 deletions src/components/SubscribersOnlyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@ 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<SubscribersOnlyModalProps> = ({
externalLink,
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
Expand All @@ -26,15 +29,18 @@ export const SubscribersOnlyModal: React.FC<SubscribersOnlyModalProps> = ({
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()
Expand All @@ -51,35 +57,33 @@ export const SubscribersOnlyModal: React.FC<SubscribersOnlyModalProps> = ({
shouldCloseOnEsc={true}
shouldAcceptOnEnter={false}
shouldCloseOnOverlayClick={true}
handleCancelRequest={handleCancelRequest}
handleAcceptRequest={handleAcceptRequest}
handleCancelRequest={clearLink}
handleAcceptRequest={clearLink}
>
<h2>
{subscriberType
? `For ${subscriberType} Subscribers Only`
: "For Subscribers Only"}
</h2>
<p>
This link leads to the private{" "}
{gitHubRepoName && gitHubRepoName.length >= 1 && (
<code>{gitHubRepoName[1]}</code>
)}{" "}
repository visible only to subscribers; everyone else will see a 404.
This link leads to the private <code>{repoName}</code> repository
visible only to {subscriberType && `${subscriberType} `}
subscribers; everyone else will see a 404.
</p>
<div>
<input type="checkbox" onClick={setDontWarnMe} />
<label>Don't warn me again</label>
</div>
<div className={styles.buttonsContainer}>
<a onClick={(e) => onRequestClose(e)} href="#">
<a onClick={onRequestClose} href="#">
Cancel
</a>
<a
href={externalLink}
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
Expand Down
136 changes: 92 additions & 44 deletions src/theme/Root.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React, { useState, useEffect } from "react"
import { SubscribersOnlyModal } from "/src/components/SubscribersOnlyModal.tsx"

const gruntworkGithubOrg = "https://github.com/gruntwork-io/"
/**
* This file is the mechanism for adding stateful logic to a docusaurus
* application since the <Root> 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 gruntworkCisRepoName = "terraform-aws-cis-service-catalog"
import React, { useState, useEffect } from "react"
import { getRepos } from "/utils"
import {
SubscribersOnlyModal,
repoNamePattern,
gruntworkGithubOrg,
} from "/src/components/SubscribersOnlyModal.tsx"

const publicGruntworkRepoNames = [
"bash-commons",
Expand Down Expand Up @@ -53,29 +62,46 @@ const publicGruntworkRepoNames = [
"website-comments",
]

const { awsCISRepos, enterpriseRepos } = getRepos()

/**
* 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 publicGruntworkRepoNames.includes(repoName)
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 listIncludesRepo(publicGruntworkRepoNames, url)
}

/**
* Checks if a link references a private Gruntwork repo
*
* @param string url
* @param {string} url
* @return {boolean}
*/
const isPrivateGruntworkRepo = (url) => {
Expand All @@ -85,33 +111,49 @@ 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() {
const listener = (event) => {
// Sometimes our links wrap components, such as Cards. In these cases, the event
// target is often a child element of the <a> we're attempting to extract the
// href data from, and so we search for the closest parent <a>. In the event that
// an <a> is clicked directly, that <a> itself will be returned.
const targetLink = event.target.closest("a")
const targetLink = event?.target?.closest("a")

if (!targetLink || !targetLink.href) {
return
}

// Allow clicks on the external GitHub link FROM the modal notices to work normally
if (targetLink.dataset.modalExempt) {
Expand All @@ -124,13 +166,25 @@ function Root({ children }) {
)

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)) {
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
}

Expand All @@ -144,9 +198,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
}
}
Expand All @@ -160,29 +213,24 @@ function Root({ children }) {
return (
<>
<SubscribersOnlyModal
showModal={displaySubscriberNotice}
showModal={!!subscriberNoticeLink}
externalLink={subscriberNoticeLink}
localStorageKey={DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY}
handleCancelRequest={() => {
setDisplaySubscriberNotice(false)
setSubscriberNoticeLink("")
}}
handleAcceptRequest={() => {
setDisplaySubscriberNotice(false)
}}
clearLink={() => setSubscriberNoticeLink("")}
/>
<SubscribersOnlyModal
showModal={displayCisNotice}
showModal={!!cisNoticeLink}
externalLink={cisNoticeLink}
localStorageKey={DONT_SHOW_CIS_GITHUB_WARNING_KEY}
subscriberType="CIS"
handleCancelRequest={() => {
setDisplayCisNotice(false)
setCisNoticeLink("")
}}
handleAcceptRequest={() => {
setDisplayCisNotice(false)
}}
clearLink={() => setCisNoticeLink("")}
/>
<SubscribersOnlyModal
showModal={!!enterpriseNoticeLink}
externalLink={enterpriseNoticeLink}
localStorageKey={DONT_SHOW_ENTERPRISE_GITHUB_WARNING_KEY}
subscriberType="Enterprise"
clearLink={() => setEnterpriseNoticeLink("")}
/>
{children}
</>
Expand Down
1 change: 1 addition & 0 deletions utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ts-commons"
31 changes: 31 additions & 0 deletions utils/ts-commons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 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 safely pull in an optional package to avoid both
* build-time and runtime errors.
*
* @return {*} {{
* awsCISRepos: string[]
* enterpriseRepos: string[]
* }}
*/
export const getRepos = (): {
awsCISRepos: string[]
enterpriseRepos: string[]
} => {
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: [],
}
}
}
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down