diff --git a/src/pages/index.jsx b/src/pages/index.jsx new file mode 100644 index 00000000..1f2fbba9 --- /dev/null +++ b/src/pages/index.jsx @@ -0,0 +1,652 @@ +import PropTypes from 'prop-types'; +import { graphql } from 'gatsby'; +import React, { useState, useEffect } from 'react'; +import IOSeo from '../components/IOSeo'; +import { css } from '@emotion/react'; +import SegmentedControl from '../components/SegmentedControl'; +import Overlay from '../components/Overlay'; +import QuickstartTile from '../components/QuickstartTile'; +import IOBanner from '../components/IOBanner'; +import { + SearchInput, + useTessen, + Button, +} from '@newrelic/gatsby-theme-newrelic'; +import { navigate } from '@reach/router'; + +import BUILD_YOUR_OWN from '../images/build-your-own.svg'; +import { useDebounce } from 'react-use'; +import { sortFeaturedQuickstarts } from '../utils/sortFeaturedQuickstarts'; +import { + QUICKSTARTS_REPO, + RESERVED_QUICKSTART_IDS, + QUICKSTARTS_COLLAPSE_BREAKPOINT, + LISTVIEW_BREAKPOINT, +} from '../data/constants'; +import CATEGORIES from '../data/instant-observability-categories'; + +import SuperTiles from '../components/SuperTiles'; + +const VIEWS = { + GRID: 'Grid view', + LIST: 'List view', +}; + +const DOUBLE_COLUMN_BREAKPOINT = '1180px'; +const TRIPLE_COLUMN_BREAKPOINT = '1350px'; +const SINGLE_COLUMN_BREAKPOINT = LISTVIEW_BREAKPOINT; + +/** + * Determines if one string is a substring of the other, case insensitive + * @param {String} substring the substring to test against + * @returns {(Function) => Boolean} Callback function that determines if the argument has the substring + */ +const stringIncludes = (substring) => (fullstring) => + fullstring.toLowerCase().includes(substring.toLowerCase()); + +/** + * Filters a quickstart based on a provided search term. + * @param {String} search Search term. + * @returns {(Function) => Boolean} Callback function to be used by filter. + */ +const filterBySearch = (search) => ({ + title, + summary, + description, + keywords, +}) => { + if (!search) { + return true; + } + + const searchIncludes = stringIncludes(search); + return ( + searchIncludes(title) || + searchIncludes(summary) || + searchIncludes(description) || + keywords.some(searchIncludes) + ); +}; + +/** + * Filters a quickstart based on a category. + * @param {String} category The category type (e.g. 'featured'). + * @returns {(Function) => Boolean} Callback function to be used by filter. + */ +const filterByCategory = (category) => { + const { associatedKeywords = [] } = + CATEGORIES.find(({ value }) => value === category) || {}; + + return (quickstart) => + !category || + (quickstart.keywords && + quickstart.keywords.find((k) => associatedKeywords.includes(k))); +}; + +const QuickstartsPage = ({ data, location }) => { + const [view, setView] = useState(VIEWS.GRID); + const tessen = useTessen(); + + const [search, setSearch] = useState(''); + const [category, setCategory] = useState(''); + + const [isCategoriesOverlayOpen, setIsCategoriesOverlayOpen] = useState(false); + + useEffect(() => { + const params = new URLSearchParams(location.search); + const searchParam = params.get('search'); + const categoryParam = params.get('category'); + + setSearch(searchParam); + setCategory(categoryParam || ''); + if (searchParam || categoryParam) { + tessen.track({ + eventName: 'instantObservability', + category: 'QuickstartCatalogSearch', + search: searchParam, + quickstartCategory: categoryParam, + }); + } + }, [location.search, tessen]); + + const closeCategoriesOverlay = () => { + setIsCategoriesOverlayOpen(false); + }; + + const handleSearch = (value) => { + if (value !== null && value !== undefined) { + const params = new URLSearchParams(location.search); + params.set('search', value); + + navigate(`?${params.toString()}`); + } + }; + + const handleCategory = (value) => { + if (value !== null && value !== undefined) { + const params = new URLSearchParams(location.search); + params.set('category', value); + + navigate(`?${params.toString()}`); + } + + closeCategoriesOverlay(); + }; + + useDebounce( + () => { + handleSearch(search); + }, + 400, + [search] + ); + + const quickstarts = data.allQuickstarts.nodes; + + const alphaSort = quickstarts.sort((a, b) => a.title.localeCompare(b.title)); + let sortedQuickstarts = sortFeaturedQuickstarts(alphaSort); + + // Hard-code for moving codestream object to front of sortedQuickstarts array - CM + if ((!category && !search) || (category === 'featured' && !search)) { + // uuid is codestream id specifically - CM + const codestreamIndex = sortedQuickstarts.findIndex( + ({ id }) => id === '29bd9a4a-1c19-4219-9694-0942f6411ce7' + ); + + if (codestreamIndex > -1) { + const codestreamObject = sortedQuickstarts[codestreamIndex]; + sortedQuickstarts = [ + codestreamObject, + ...sortedQuickstarts.slice(0, codestreamIndex), + ...sortedQuickstarts.slice(codestreamIndex + 1), + ]; + } + } + + const filteredQuickstarts = sortedQuickstarts + .filter(filterBySearch(search)) + .filter(filterByCategory(category)); + + const categoriesWithCount = CATEGORIES.map((cat) => ({ + ...cat, + count: quickstarts + .filter(filterBySearch(search)) + .filter(filterByCategory(cat.value)).length, + })); + + /** + * Finds display name for selected category. + * @returns {String} Display name for results found. + */ + const getDisplayName = (defaultName = 'All quickstarts') => { + const found = CATEGORIES.find((cat) => cat.value === category); + + if (!found.value) return defaultName; + + return found.displayName; + }; + + return ( + <> + + +
+ +
+
+ +
+
+ setSearch('')} + onChange={(e) => setSearch(e.target.value)} + css={css` + --svg-color: var(--color-neutrals-700); + box-shadow: none; + max-width: 630px; + svg { + width: 16px; + height: 16px; + color: var(--svg-color); + } + + .dark-mode & { + --svg-color: var(--primary-text-color); + } + + @media screen and (max-width: ${QUICKSTARTS_COLLAPSE_BREAKPOINT}) { + font-size: 11px; + max-width: 100%; + } + `} + /> + { + setView(view); + + tessen.track({ + eventName: 'instantObservability', + category: 'QuickstartViewToggle', + quickstartViewState: view, + }); + }} + css={css` + @media screen and (max-width: ${LISTVIEW_BREAKPOINT}) { + display: none; + } + `} + /> +
+
+ + +
+

+ Category +

+
+ {categoriesWithCount.map(({ displayName, value, count }) => ( + + ))} +
+
+ +
+
+
+
+
+ + Showing {filteredQuickstarts.length} results + for: + {search || getDisplayName()} + +
+
+ + {filteredQuickstarts.map((pack) => ( + + ))} +
+
+
+ + ); +}; + +QuickstartsPage.propTypes = { + data: PropTypes.object.isRequired, + location: PropTypes.object, +}; + +export const pageQuery = graphql` + query { + allQuickstarts { + nodes { + fields { + slug + } + id + title + name + websiteUrl + logoUrl + packUrl + level + keywords + dashboards { + description + name + screenshots + url + } + alerts { + details + name + url + type + } + documentation { + name + url + description + } + authors + description + summary + installPlans { + id + name + } + } + } + } +`; + +const Label = ({ children, htmlFor }) => ( + +); + +Label.propTypes = { + children: PropTypes.node, + htmlFor: PropTypes.string, +}; + +const FormControl = ({ children }) => ( +
+ {children} +
+); + +FormControl.propTypes = { + children: PropTypes.node, +}; + +export default QuickstartsPage;