From 71bdd714c0f1e7dc1313078fd52214868c7a7a7b Mon Sep 17 00:00:00 2001 From: aswanson-nr Date: Fri, 10 Sep 2021 17:04:17 -0700 Subject: [PATCH] feat: simplify search / filtering --- src/components/PackImg.js | 22 +- src/components/PackTile.js | 33 +- .../quickstarts/QuickstartGridList.js | 81 ---- src/data/constants.js | 5 - src/pages/instant-observability.js | 372 +++++++++--------- 5 files changed, 217 insertions(+), 296 deletions(-) delete mode 100644 src/components/quickstarts/QuickstartGridList.js diff --git a/src/components/PackImg.js b/src/components/PackImg.js index f38937be4..ea6b43e20 100644 --- a/src/components/PackImg.js +++ b/src/components/PackImg.js @@ -1,13 +1,23 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { css } from '@emotion/react'; import DEFAULT_IMAGE from '../images/default-logo-background.svg'; -const createPackAcronym = (name) => - name.split(' ').reduce((acc, word) => `${acc}${word.charAt(0)}`, ''); - const PackImg = ({ className, logoUrl, packName }) => { - const packAcronym = createPackAcronym(packName); + const [packAcronym, setPackAcronym] = useState(''); + + const getPackNameAcronym = () => { + let packNameAcronym = ''; + packName.split(' ').forEach((word) => { + packNameAcronym = packNameAcronym.concat('', word.charAt(0)); + }); + setPackAcronym(packNameAcronym.toUpperCase()); + }; + useEffect(() => { + if (!logoUrl) { + getPackNameAcronym(); + } + }); if (logoUrl) { return ( @@ -41,7 +51,7 @@ const PackImg = ({ className, logoUrl, packName }) => { align-items: center; `} > -

{packAcronym.toUpperCase()}

+

{packAcronym}

); }; diff --git a/src/components/PackTile.js b/src/components/PackTile.js index d2f504b12..2b8535af9 100644 --- a/src/components/PackTile.js +++ b/src/components/PackTile.js @@ -10,15 +10,15 @@ import { useInstrumentedHandler, } from '@newrelic/gatsby-theme-newrelic'; import PackImg from './PackImg'; -import { - QUICKSTART_SUPPORT_LEVELS, - QUICKSTART_CATALOG_VIEWS, -} from '../data/constants'; -const SHIELD_LEVELS = [ - QUICKSTART_SUPPORT_LEVELS.NEWRELIC, - QUICKSTART_SUPPORT_LEVELS.VERIFIED, -]; +const LEVELS = { + NEWRELIC: 'NEWRELIC', +}; + +const VIEWS = { + GRID: 'Grid view', + LIST: 'List view', +}; const PackTile = ({ id, @@ -70,7 +70,7 @@ const PackTile = ({ css={css` overflow: hidden; - ${view === QUICKSTART_CATALOG_VIEWS.LIST && + ${view === VIEWS.LIST && css` display: flex; margin-bottom: 1em; @@ -85,11 +85,11 @@ const PackTile = ({ height: 200px; background-color: var(--color-white); object-fit: scale-down; - width: ${view === QUICKSTART_CATALOG_VIEWS.GRID ? 100 : 25}%; - padding: 0 ${view === QUICKSTART_CATALOG_VIEWS.GRID ? 5 : 1}%; - margin: ${view === QUICKSTART_CATALOG_VIEWS.GRID ? 'auto' : 0}; + width: ${view === VIEWS.GRID ? 100 : 25}%; + padding: 0 ${view === VIEWS.GRID ? 5 : 1}%; + margin: ${view === VIEWS.GRID ? 'auto' : 0}; - ${view === QUICKSTART_CATALOG_VIEWS.LIST && + ${view === VIEWS.LIST && css` max-height: 150px; @@ -103,7 +103,7 @@ const PackTile = ({ css={css` padding: 1em; - ${view === QUICKSTART_CATALOG_VIEWS.LIST && + ${view === VIEWS.LIST && css` width: 75%; @@ -114,8 +114,7 @@ const PackTile = ({ `} >

- {name}{' '} - {SHIELD_LEVELS.includes(level) && } + {name} {level === LEVELS.NEWRELIC && }

{ - return ( - <> -

- Showing {quickstarts.length} results -
-
- - - - {quickstarts.map((pack) => ( - - ))} -
- - ); -}; - -QuickstartGridList.propTypes = { - quickstarts: PropTypes.arrayOf(quickstart).isRequired, - view: PropTypes.oneOf(Object.values(QUICKSTART_CATALOG_VIEWS)), -}; - -export default QuickstartGridList; diff --git a/src/data/constants.js b/src/data/constants.js index 3a77be2a7..0637044b5 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -49,11 +49,6 @@ export const QUICKSTART_ALERT_TYPES = { STATIC: 'STATIC', }; -export const QUICKSTART_CATALOG_VIEWS = { - GRID: 'GRID', - LIST: 'LIST', -}; - export const QUICKSTARTS_REPO = 'https://github.com/newrelic/newrelic-observability-packs'; diff --git a/src/pages/instant-observability.js b/src/pages/instant-observability.js index 8ef68abb5..fd819a595 100644 --- a/src/pages/instant-observability.js +++ b/src/pages/instant-observability.js @@ -2,135 +2,118 @@ import PropTypes from 'prop-types'; import { graphql } from 'gatsby'; import React, { useState, useEffect } from 'react'; import useMobileDetect from 'use-mobile-detect-hook'; -import { useQueryParams, StringParam } from 'use-query-params'; import DevSiteSeo from '../components/DevSiteSeo'; import { css } from '@emotion/react'; import SegmentedControl from '../components/SegmentedControl'; -import QuickstartGridList from '../components/quickstarts/QuickstartGridList'; -import { QUICKSTART_CATALOG_VIEWS } from '../data/constants'; -import MobileQSFilter from '../components/MobileQSFilter'; +import PackTile from '../components/PackTile'; +import Select from '../components/Select'; import { SearchInput, useTessen, + ExternalLink, Button, Icon, } from '@newrelic/gatsby-theme-newrelic'; -import { useDebounce } from 'react-use'; +import { navigate } from '@reach/router'; +import BUILD_YOUR_OWN from '../images/build-your-own.svg'; import { useDebounce } from 'react-use'; -import { useQueryParams, StringParam } from 'use-query-params'; - -const VIEWS = [ - { - label: 'Grid view', - value: QUICKSTART_CATALOG_VIEWS.GRID, - }, - { - label: 'List view', - value: QUICKSTART_CATALOG_VIEWS.LIST, - }, + +const { QUICKSTARTS_REPO } = require('../data/constants'); + +const FILTERS = [ + { name: 'All', type: '', icon: 'nr-all-entities' }, + { name: 'Dashboards', type: 'dashboards', icon: 'nr-dashboard' }, + { name: 'Alerts', type: 'alerts', icon: 'nr-alert' }, + { name: 'Data sources', type: 'documentation', icon: 'nr-document' }, ]; -const prop = (key) => (obj) => obj[key]; -const withComponent = (packs, key) => packs.filter((p) => p[key].length > 0); +const VIEWS = { + GRID: 'Grid view', + LIST: 'List view', +}; + +/** + * Filters a quickstart based on a provided search term. + * @param {String} search Search term. + * @returns {(Object) => Boolean} Callback function to be used by filter. + */ +const filterBySearch = (search) => ({ name, description }) => { + return ( + !search || + name.toLowerCase().includes(search.toLowerCase()) || + description.toLowerCase().includes(search.toLowerCase()) + ); +}; + +/** + * Filters a quickstart based on a content type. + * @param {String} type The content type (e.g. 'alerts'). + * @returns {(Object) => Boolean} Callback function to be used by filter. + */ +const filterByContentType = (type) => (quickstart) => { + return !type || (quickstart[type] && quickstart[type].length > 0); +}; const QuickstartsPage = ({ data, location }) => { - const tessen = useTessen(); + const [view, setView] = useState(VIEWS.GRID); const detectMobile = useMobileDetect(); - const isMobile = detectMobile.isMobile(); + const tessen = useTessen(); - const [isClient, setClient] = useState(false); + const [search, setSearch] = useState(''); + const [filter, setFilter] = useState(''); useEffect(() => { - setClient(true); - }, []); + const params = new URLSearchParams(location.search); + const searchParam = params.get('search'); + const filterParam = params.get('filter'); + setSearch(searchParam); + setFilter(filterParam); + }, [location.search]); - const { - allQuickstarts: { nodes: quickstarts }, - } = data; + const handleFilter = (value) => { + setFilter(value); + const params = new URLSearchParams(location.search); + params.set('filter', value); + navigate(`?${params.toString()}`); + }; + + const handleSearch = (value) => { + if (value !== null && value !== undefined) { + const params = new URLSearchParams(location.search); + params.set('search', value); + + navigate(`?${params.toString()}`); + } + }; - const [view, setView] = useState(QUICKSTART_CATALOG_VIEWS.GRID); - useEffect(() => { - setView(view); - }, [view]); - - const [queryParams, setQueryParams] = useQueryParams({ - search: StringParam, - filter: StringParam, - }); - const [formState, setFormState] = useState({ - search: queryParams.search || '', - filter: queryParams.filter || '', - }); - - // This is purely to prevent sending incomplete search events - // to tessen useDebounce( () => { - if (formState.search && formState.search !== '') { - tessen.track('observabilityPack', `packSearch`, { - packSearchTerm: formState.search, - }); - if (typeof window !== 'undefined' && window.newrelic) { - window.newrelic.addPageAction('packSearch', { - packSearchTerm: formState.search, - }); - } - } + handleSearch(search); }, - 1000, - [formState] + 400, + [search] ); - // Updates the url based on the current form state - useEffect(() => { - setQueryParams(formState, 'replace'); - }, [formState, setQueryParams]); + const quickstarts = data.allQuickstarts.nodes; - let filteredPacks = quickstarts.filter( - (qs) => - qs.name.toLowerCase().includes(formState.search.toLowerCase()) || - qs.description.toLowerCase().includes(formState.search.toLowerCase()) - ); + const filteredQuickstarts = quickstarts + .filter(filterBySearch(search)) + .filter(filterByContentType(filter)); - // This array is used to populate filters with a name, value, - // and count of packs within each filter - const packContentsFilterValues = [ - { - filterName: 'All', - filterValue: 'all', - filterCount: filteredPacks.length, - iconName: 'nr-all-entities', - }, - { - filterName: 'Dashboards', - filterValue: 'dashboards', - filterCount: withComponent(filteredPacks, 'dashboards').length, - iconName: 'nr-dashboard', - }, - { - filterName: 'Alerts', - filterValue: 'alerts', - filterCount: withComponent(filteredPacks, 'alerts').length, - iconName: 'nr-alerts', - }, - { - filterName: 'Data sources', - filterValue: 'documentation', - filterCount: withComponent(filteredPacks, 'documentation').length, - iconName: 'nr-document', - }, - ]; - - if ( - formState.filter !== 'all' && - packContentsFilterValues.map(prop('filterValue')).includes(formState.filter) - ) { - filteredPacks = withComponent(filteredPacks, formState.filter); - } + const filtersWithCount = FILTERS.map((filter) => ({ + ...filter, + count: quickstarts + .filter(filterBySearch(search)) + .filter(filterByContentType(filter.type)).length, + })); return ( <> - +
{ `} /> - //START my code - {/* */} - {/* */} - // END my code // START LIZ CODE - - {isMobile ? ( - + + {detectMobile.isMobile() ? ( + ) : ( - packContentsFilterValues.map( - ({ filterName, filterValue, filterCount, iconName }, i) => ( - - ) - ) + /> + {`${name} (${count})`} + + )) )} - // END LIZ CODE
@@ -274,6 +235,7 @@ const QuickstartsPage = ({ data, location }) => {
{
{ > { - setFormState({ ...formState, search: '' }); - }} + value={search} + placeholder="Search pack names / descriptions" + onClear={() => setSearch('')} onChange={(e) => { - setFormState({ ...formState, search: e.target.value }); + const value = e.target.value; + setSearch(value); }} />
@@ -326,11 +288,11 @@ const QuickstartsPage = ({ data, location }) => { css={css` display: inline-block; min-width: 155px; - margin-left: 1rem; + margin-left: 20px; `} > { setView(view); @@ -342,11 +304,57 @@ const QuickstartsPage = ({ data, location }) => {
- {isClient ? ( - - ) : ( - - )} +
+ Showing {filteredQuickstarts.length} results +
+
+ + + + {filteredQuickstarts.map((pack) => ( + + ))} +
@@ -372,20 +380,10 @@ export const pageQuery = graphql` packUrl level dashboards { - name - url description - } - alerts { name - details + screenshots url - type - } - documentation { - name - url - description } alerts { details