diff --git a/.circleci/config.yml b/.circleci/config.yml index 81d3fe154f..60d8bed67e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,6 +48,7 @@ references: export AWS_SECRET_ACCESS_KEY=`echo ${CREDENTIALS} | jq -r '.Credentials.SecretAccessKey'` export AWS_SESSION_TOKEN=`echo ${CREDENTIALS} | jq -r '.Credentials.SessionToken'` export AWS_EXPIRATION=`echo ${CREDENTIALS} | jq -r '.Credentials.Expiration'` + export ALGOLIA_LIBRARY_INDEX_NAME="stage_docs_sourcer-library-reference" ./scripts/push-to-s3-stage.sh deploy_to_prod: &deploy-to-prod @@ -60,6 +61,7 @@ references: export AWS_SECRET_ACCESS_KEY=`echo ${CREDENTIALS} | jq -r '.Credentials.SecretAccessKey'` export AWS_SESSION_TOKEN=`echo ${CREDENTIALS} | jq -r '.Credentials.SessionToken'` export AWS_EXPIRATION=`echo ${CREDENTIALS} | jq -r '.Credentials.Expiration'` + export ALGOLIA_LIBRARY_INDEX_NAME="prod_docs_sourcer-library-reference" ./scripts/push-to-s3-prod.sh notify_slack_staging: ¬ify_slack_staging diff --git a/_docs-sources/iac/reference/index.md b/_docs-sources/iac/reference/index.md index 0b2d8a653b..c667410b7c 100644 --- a/_docs-sources/iac/reference/index.md +++ b/_docs-sources/iac/reference/index.md @@ -1,7 +1,7 @@ -# Library Reference +import SearchArea from "/src/components/SearchArea" -The Library Reference serves as the definitive index for all actively maintained Modules and Services within the Gruntwork Infrastructure as Code Library. This comprehensive reference provides a dedicated page for each module and service providing descriptions, detailed information on input and output variables, and sample code to help you get started. +# Find a module -If you're already familiar with the IaC Library and are ready to dive right in, you can find the full Service Catalog and Module catalog reference in the left sidebar. +This section contains a complete list of the modules, services, and tools included in the Gruntwork IaC Library, along with reference documentation for each. For general information on the structure and usage of the Library, refer to our [IaC Library docs](../overview/index.md). If you can't find a module or service that suits your needs, let us know at feedback@gruntwork.io. -For an introduction to the Gruntwork IaC Library, check out the [Overview](/iac/overview) page. This page introduces the concept of Modules and Services, clarifies their respective purposes, and offers guidance on when and how to effectively utilize them. The overview is a great starting point for understanding what the library can offer and how to best navigate it. \ No newline at end of file + diff --git a/config/custom-environment-variables.yaml b/config/custom-environment-variables.yaml index 55951dd77e..a6a69f231c 100644 --- a/config/custom-environment-variables.yaml +++ b/config/custom-environment-variables.yaml @@ -18,6 +18,7 @@ algolia: appId: ALGOLIA_APP_ID apiKey: ALGOLIA_API_KEY indexName: ALGOLIA_INDEX_NAME + libraryIndexName: ALGOLIA_LIBRARY_INDEX_NAME # Posthog Analytics tracking config posthog: diff --git a/config/default.yaml b/config/default.yaml index cd817e3dfa..d1b00e0fae 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -4,6 +4,7 @@ algolia: appId: 7AWZHGNJE2 apiKey: f799a6e9a302535bd92bbfab65fb6311 # This is a search only API key. It's safe to check in. indexName: docs_site_dev + libraryIndexName: dev_docs_sourcer-library-reference googleAnalytics: trackingID: UA-154792164-4 diff --git a/docs/iac/reference/index.md b/docs/iac/reference/index.md index d1be2d7937..4075c73ace 100644 --- a/docs/iac/reference/index.md +++ b/docs/iac/reference/index.md @@ -1,14 +1,15 @@ -# Library Reference +import SearchArea from "/src/components/SearchArea" -The Library Reference serves as the definitive index for all actively maintained Modules and Services within the Gruntwork Infrastructure as Code Library. This comprehensive reference provides a dedicated page for each module and service providing descriptions, detailed information on input and output variables, and sample code to help you get started. +# Find a module -If you're already familiar with the IaC Library and are ready to dive right in, you can find the full Service Catalog and Module catalog reference in the left sidebar. +This section contains a complete list of the modules, services, and tools included in the Gruntwork IaC Library, along with reference documentation for each. For general information on the structure and usage of the Library, refer to our [IaC Library docs](../overview/index.md). If you can't find a module or service that suits your needs, let us know at feedback@gruntwork.io. + + -For an introduction to the Gruntwork IaC Library, check out the [Overview](/iac/overview) page. This page introduces the concept of Modules and Services, clarifies their respective purposes, and offers guidance on when and how to effectively utilize them. The overview is a great starting point for understanding what the library can offer and how to best navigate it. diff --git a/docusaurus.config.js b/docusaurus.config.js index 8b3257cc00..c1bcfddb51 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -47,7 +47,9 @@ const config = { stylesheets: [ "https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600;1,700&display=swap", ], - + customFields: { + libraryIndexName: algoliaConfig ? algoliaConfig.libraryIndexName : undefined + }, presets: [ [ "@docusaurus/preset-classic", @@ -291,6 +293,7 @@ const config = { // Public API key: safe to commit, but still sourced from config apiKey: algoliaConfig.apiKey, indexName: algoliaConfig.indexName, + libraryIndexName: algoliaConfig.libraryIndexName, contextualSearch: true, } : undefined, diff --git a/package.json b/package.json index 6f0e80fc1f..a198e8197b 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", + "react-select": "^5.7.3", "ts-jest": "^27.1.3", "url-loader": "^4.1.1" }, @@ -53,7 +54,7 @@ "yargs": "^17.4.0" }, "optionalDependencies": { - "docs-sourcer": "git+ssh://git@github.com/gruntwork-io/docs-sourcer.git#v0.16", + "docs-sourcer": "git+ssh://git@github.com/gruntwork-io/docs-sourcer.git#v0.18", "ts-commons": "gruntwork-io/ts-commons#v3.2.1" }, "browserslist": { diff --git a/scripts/update-search-index.sh b/scripts/update-search-index.sh index 9b51c79171..54ff82a8b9 100755 --- a/scripts/update-search-index.sh +++ b/scripts/update-search-index.sh @@ -84,7 +84,7 @@ function index_docs() { echo "Updating search index with docs-sourcer using prefix: $index_prefix..." # Update Algolia index using docs-sourcer. This is the new method of indexing. # In order to add plugins to this list just space separate them. - ALGOLIA_APP_ID="$ALGOLIA_APPLICATION_ID" ALGOLIA_API_KEY="$api_key" ALGOLIA_INDEX_PREFIX="$index_prefix" yarn regenerate --plugins docs-indexer + ALGOLIA_APP_ID="$ALGOLIA_APPLICATION_ID" ALGOLIA_API_KEY="$api_key" ALGOLIA_INDEX_PREFIX="$index_prefix" yarn regenerate --plugins docs-indexer module-catalog-api-index service-catalog-api-index # Update Algolia index using docsearch-scraper. This is the old method of indexing. We are temporarily running this # alongside the docs-sourcer index. diff --git a/sidebars/library-reference.js b/sidebars/library-reference.js index 06e2ef03e2..56ae73c9d8 100644 --- a/sidebars/library-reference.js +++ b/sidebars/library-reference.js @@ -5,7 +5,7 @@ const sidebar = [ collapsible: false, items: [ { - label: "Library Reference", + label: "Find a module", type: "doc", id: "iac/reference/index", }, diff --git a/src/components/SearchArea.module.css b/src/components/SearchArea.module.css new file mode 100644 index 0000000000..7d77aa0cde --- /dev/null +++ b/src/components/SearchArea.module.css @@ -0,0 +1,120 @@ +/* BASIC STRUCTURE */ + +.container { + max-width: 600px; + margin-top: 4rem; +} + +.card_container_max_width { + min-width: 300px; + max-width: 300px; + max-height: 240px; + margin: 0 auto; +} + +.card_result { + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: 13px; +} + +.SearchContainerItem { + width: 300px; +} + +.SearchContainerDropdown { + width: 300px; +} + +.NoResultsContainer { + max-width: 600px; + align-items: center; +} + +.NoResults { + font-size: large; + color: rgba(40, 40, 40, 0.628); + font-style: italic; +} + +.SearchContainerItemHeader { + font-size: x-small; + align-items: start; + font-weight: 600; + color: rgba(40, 40, 40, 0.628); + margin-bottom: 0; +} + +.SearchInputContainer { + height: 40.40px; + margin-top: 1px; + padding-bottom: 1px; + padding-right: 1px; +} + +.SearchInput { + align-items: center; + background: rgb(255, 255, 255); + border-width: 1px; + border-style: solid; + border-color: #CCCCCC; + border-radius: 4px; + -webkit-border-radius: 4px; + padding-top: 0px; + color: hsl(0, 0%, 20%); + display: flex; + font-weight: 400; + justify-content: space-between; + padding: 2px 8px; + -webkit-user-select: none; + user-select: none; + width: 100%; + height: 100%; + margin-left: 1px; + margin-top: 0px; + font-size: 15px; + font-family: "Source Sans Pro"; +} + +.SearchInput:focus-visible { + outline: unset; + border-color: #6f5bd7; + box-shadow: 0 0 0 1px #6f5bd7; +} + + +.SearchInput::-webkit-input-placeholder { + color: #969faf; + opacity: unset; +} + +.SearchInput::placeholder { + color: #969faf; +} + +.FacetListContainer { + position:absolute; +} + +@media only screen and (max-width: 640px) { + .card_container_max_width { + width: 100%; + max-width: unset; + } + .container { + width: 100% + } + .SearchContainerItem { + width: 100% + } + .SearchContainerDropdown { + width: 100% + } + .FacetListContainer { + width: 100%; + padding-right: 1.7rem; + } +} diff --git a/src/components/SearchArea.tsx b/src/components/SearchArea.tsx new file mode 100644 index 0000000000..969f5ef841 --- /dev/null +++ b/src/components/SearchArea.tsx @@ -0,0 +1,226 @@ +import algoliasearch from "algoliasearch/lite" +import React, { PropsWithChildren, useEffect, useRef, useState } from "react" +import Select from "react-select" + +import { Card } from "./Card" +import { CardGroup } from "./CardGroup" +import styles from "./SearchArea.module.css" +import Grid from "./Grid" +import useDocusaurusContext from "@docusaurus/useDocusaurusContext" + +interface SearchAreaProps { + name: string + requirement?: "required" | "optional" + type: string +} + +function ResultCardGroup(hits: any) { + return ( + + {hits.map((hit: any) => { + return ( + +
+
+ ) + })} +
+ ) +} + +function NoResults() { + return ( +
+

+ No results found, please try another search +

+
+ ) +} + +function CustomHits(hits: any[]) { + let newHits = hits["hits"]["searchHits"] + + if (newHits.length === 0) { + return NoResults() + } + + /* + Don't display search results where the module has been deprecated. We prefix the friendly name for these + modules with [DEPRECATED], so we filter for all hits where there is not a match for the prefix. + */ + const activeHits = newHits.filter( + (hit: any) => !hit.moduleName.startsWith("[DEPRECATED]") + ) + + /* + Don't display modules where the description contains a note. + These modules all state they are not intended to be used directly, and the module description breaks out of the card + */ + const onlyUseableModules = activeHits.filter( + (hit: any) => hit.moduleDescription.toLowerCase().indexOf("note") === -1 + ) + + return onlyUseableModules.length > 0 + ? ResultCardGroup(onlyUseableModules) + : NoResults() +} + +const selectStyles = { + placeholder: (base) => { + return { + ...base, + color: "#969faf", + } + }, + control: (base, state) => ({ + ...base, + fontSize: "16px", + borderColor: state.isFocused ? "#6f5bd7" : "#CCCCCC", + boxShadow: state.isFocused ? "0 0 0 1px #6f5bd7" : "#CCCCCC", + "&:hover": { + borderColor: state.isFocused ? "#6f5bd7" : "#CCCCCC", + boxShadow: state.isFocused ? "0 0 0 1px #6f5bd7" : "#CCCCCC", + }, + }), +} + +export const SearchArea: React.FunctionComponent< + PropsWithChildren +> = ({ name, requirement, type, children }) => { + const { siteConfig } = useDocusaurusContext() + + const algoliaAppId: string = siteConfig.themeConfig.algolia.appId + // This key is for search only. It is safe to check in. + const algoliaSearchKey: string = "a976ea48057ceaa662656ec8f4f591af" + const indexName: string = siteConfig.themeConfig.algolia.libraryIndexName + + const searchClient = algoliasearch(algoliaAppId, algoliaSearchKey) + const index = searchClient.initIndex(indexName) + + const [searchTerm, setSearchTerm] = useState("") + const [facetFilters, setFacetFilters] = useState([]) + + const [searchHits, setSearchHits] = useState([]) + const [searchTypeFacets, setSearchTypeFacets] = useState([]) + + const [isLoading, setIsLoading] = useState(true) + + const handleSearchFacets = (facets: {}): Object[] => { + const facetArray = [] + + for (const k in facets) { + let v = facets[k] + facetArray.push({ key: k, value: v }) + } + + return facetArray + } + + const loadSearchHits = async () => { + setIsLoading(true) + + await index + .search(searchTerm, { + facets: ["type"], + facetFilters: facetFilters, + hitsPerPage: 300, + }) + .then((resp) => { + setSearchHits(resp["hits"]) + + // Only load the facets once - when the page loads + if (searchTypeFacets.length == 0) { + setSearchTypeFacets(handleSearchFacets(resp["facets"]["type"])) + } + }) + } + + useEffect(() => { + loadSearchHits() + }, []) + + useEffect(() => { + setIsLoading(false) + }, [searchHits]) + + useEffect(() => { + const timeOutId = setTimeout(() => loadSearchHits(), 200) + return () => clearTimeout(timeOutId) + }, [searchTerm]) + + useEffect(() => { + loadSearchHits() + }, [facetFilters]) + + const onSearch = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value) + } + + const selectRepoTitleFacet = (type: any) => { + if (!type) { + // Unset - we always want to be scoped to modules + setFacetFilters([]) + } else { + setFacetFilters([`type:${type.value}`]) + } + } + + const inputElement = useRef(null); + useEffect(() => { + if (inputElement.current) { + inputElement.current.focus(); + } + }, []); + + + return ( +
+ +
+

SEARCH

+
+ +
+
+
+

TYPE

+
+