From 28c97df9d2f354cace3fe451393cc75bce33e9bf Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Thu, 18 Jun 2020 10:39:24 -0700 Subject: [PATCH 01/24] feat: Add a search icon --- src/components/FeatherIcon.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/FeatherIcon.js b/src/components/FeatherIcon.js index dc31050d5..9a3bc5e04 100644 --- a/src/components/FeatherIcon.js +++ b/src/components/FeatherIcon.js @@ -42,6 +42,12 @@ const ICONS = { github: ( ), + search: ( + <> + + + + ), }; FeatherIcon.propTypes = { From ac158ad697a8d24298199731ac271ecebdd86288 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Thu, 18 Jun 2020 11:33:08 -0700 Subject: [PATCH 02/24] feat: Create a SearchInput component --- src/components/SearchInput.js | 18 ++++++++++++++++++ src/components/SearchInput.module.scss | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/components/SearchInput.js create mode 100644 src/components/SearchInput.module.scss diff --git a/src/components/SearchInput.js b/src/components/SearchInput.js new file mode 100644 index 000000000..bc6dbb4d9 --- /dev/null +++ b/src/components/SearchInput.js @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import FeatherIcon from './FeatherIcon'; +import styles from './SearchInput.module.scss'; + +const SearchInput = ({ className, ...props }) => ( +
+ + +
+); + +SearchInput.propTypes = { + className: PropTypes.string, +}; + +export default SearchInput; diff --git a/src/components/SearchInput.module.scss b/src/components/SearchInput.module.scss new file mode 100644 index 000000000..bf48fcaef --- /dev/null +++ b/src/components/SearchInput.module.scss @@ -0,0 +1,19 @@ +.container { + display: inline-flex; + align-items: center; + width: 100%; + position: relative; +} + +.icon { + position: absolute; + right: 0.5rem; + stroke: var(--color-neutrals-700); +} + +.input { + width: 100%; + font-size: 0.875rem; + padding: 0.5rem; + padding-right: calc(1rem + 1em); +} From ff4faea6dfbb914d87bf9153805a379230e73094 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Thu, 18 Jun 2020 11:38:35 -0700 Subject: [PATCH 03/24] feat: Add the search input to the sidebar --- src/components/Sidebar.js | 29 ++++++++++++++++++++--------- src/components/Sidebar.module.scss | 4 ++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index b7c5205dd..524b08a21 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -1,19 +1,30 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Link } from 'gatsby'; import cx from 'classnames'; import Logo from './Logo'; import Navigation from './Navigation'; +import SearchInput from './SearchInput'; import styles from './Sidebar.module.scss'; -const Sidebar = ({ className }) => ( - -); +const Sidebar = ({ className }) => { + const [searchTerm, setSearchTerm] = useState(''); + + return ( + + ); +}; Sidebar.propTypes = { className: PropTypes.string, diff --git a/src/components/Sidebar.module.scss b/src/components/Sidebar.module.scss index f007215b5..c5a7a93e9 100644 --- a/src/components/Sidebar.module.scss +++ b/src/components/Sidebar.module.scss @@ -19,3 +19,7 @@ .nav { margin-top: 1rem; } + +.searchInput { + margin: 1rem 0; +} From 17671e4751db01a0b976ce9dc495bde5a5300544 Mon Sep 17 00:00:00 2001 From: Cayla Hamann Date: Sun, 21 Jun 2020 21:27:05 -0400 Subject: [PATCH 04/24] chore: refactor --- src/components/Navigation.js | 49 +++++++++++++++++++++++---- src/components/Navigation.module.scss | 14 ++++++++ src/utils/matchSearchString.js | 5 +++ 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 src/utils/matchSearchString.js diff --git a/src/components/Navigation.js b/src/components/Navigation.js index d74fe789a..cdd2a1395 100644 --- a/src/components/Navigation.js +++ b/src/components/Navigation.js @@ -5,11 +5,12 @@ import cx from 'classnames'; import { BreadcrumbContext } from './BreadcrumbContext'; import FeatherIcon from './FeatherIcon'; import pages from '../data/sidenav.json'; +import matchSearchString from '../utils/matchSearchString'; import styles from './Navigation.module.scss'; // recursively create navigation -const renderNav = (pages, depthLevel = 0) => { +const renderNav = (pages, searches, depthLevel = 0) => { const crumbs = useContext(BreadcrumbContext).flatMap((x) => x.displayName); const groupedPages = pages.reduce((groups, page) => { @@ -28,15 +29,29 @@ const renderNav = (pages, depthLevel = 0) => { )} {pages.map((page) => { const [isExpanded, setIsExpanded] = useState( - crumbs.length === depthLevel || crumbs.includes(page.displayName) + searches !== undefined || + crumbs.includes(page.displayName) || + crumbs.length === depthLevel ); + const isCurrentPage = crumbs[crumbs.length - 1] === page.displayName; return (
  • {page.url ? ( @@ -65,7 +80,7 @@ const renderNav = (pages, depthLevel = 0) => { [styles.isExpanded]: isExpanded, })} > - {renderNav(page.children, depthLevel + 1)} + {renderNav(page.children, searches, depthLevel + 1)} )}
  • @@ -75,20 +90,42 @@ const renderNav = (pages, depthLevel = 0) => { )); }; -const Navigation = ({ className }) => { +const searchPages = (pages, searchTerm, parent = []) => { + return [ + ...new Set( + pages.flatMap((page) => { + if (page.children) { + return searchPages(page.children, searchTerm, [ + ...parent, + page.displayName, + ]); + } else if (matchSearchString(page.displayName, searchTerm)) { + return [...parent, page.displayName]; + } else if (parent.some((el) => matchSearchString(el, searchTerm))) { + return [...parent]; + } + }) + ), + ]; +}; + +const Navigation = ({ className, searchTerm }) => { + const searches = + searchTerm !== '' ? searchPages(pages, searchTerm) : undefined; return ( ); }; Navigation.propTypes = { className: PropTypes.string, + searchTerm: PropTypes.string, }; export default Navigation; diff --git a/src/components/Navigation.module.scss b/src/components/Navigation.module.scss index 9bb71c303..f747b3129 100644 --- a/src/components/Navigation.module.scss +++ b/src/components/Navigation.module.scss @@ -64,3 +64,17 @@ button.navLink { .isCurrentPage { font-weight: bold; } + +.isNotSearch { + display: none; +} + +.isSearch { + display: block; +} + +.isBeingSearched { + ul { + display: block; + } +} diff --git a/src/utils/matchSearchString.js b/src/utils/matchSearchString.js new file mode 100644 index 000000000..0b434680c --- /dev/null +++ b/src/utils/matchSearchString.js @@ -0,0 +1,5 @@ +const matchSearchString = (str, searchTerm) => { + return str.toLowerCase().includes(searchTerm.toLowerCase()); +}; + +export default matchSearchString; From 3a31d3005923da5480eb31b63f7ab6e49f95ed5a Mon Sep 17 00:00:00 2001 From: Cayla Hamann Date: Sun, 21 Jun 2020 21:33:41 -0400 Subject: [PATCH 05/24] feat: working search box --- .eslintrc.js | 1 + package-lock.json | 16 +++- package.json | 2 + src/components/GuideTile.module.scss | 5 +- src/components/IconGallery.js | 3 +- src/components/Layout.js | 1 + src/components/Layout.module.scss | 1 - src/components/Navigation.js | 25 +++++- src/components/Navigation.module.scss | 14 ++++ src/components/Step.module.scss | 2 +- src/components/Video.module.scss | 1 - src/components/styles.scss | 46 +++------- src/data/sidenav.json | 27 +++--- src/hooks/useApiDoc.js | 2 +- src/hooks/useComponentDoc.js | 2 +- .../workflow-automation.mdx | 84 +++++++++++++++++++ src/markdown-pages/query-and-store-data.mdx | 26 ++---- src/markdown-pages/query-data-nrql.mdx | 20 ++--- .../get-started-nerdgraph-api-explorer.mdx | 14 ++-- src/markdown-pages/reference/intro-to-sdk.mdx | 78 ++++++++--------- .../reference/nerdpack-file-structure.mdx | 76 ++++++++--------- src/pages/explore-data.js | 42 ++++++---- src/pages/index.js | 38 +++++++-- src/pages/index.module.scss | 5 +- src/templates/GuideTemplate.module.scss | 8 ++ 25 files changed, 336 insertions(+), 203 deletions(-) create mode 100644 src/markdown-pages/automate-workflows/workflow-automation.mdx diff --git a/.eslintrc.js b/.eslintrc.js index 849758db1..d8c2b04e1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { 'plugin:@newrelic/eslint-plugin-newrelic/react', 'plugin:@newrelic/eslint-plugin-newrelic/prettier', 'plugin:jsx-a11y/recommended', + 'plugin:react-hooks/recommended', ], // https://github.com/yannickcr/eslint-plugin-react#configuration plugins: ['react', 'jsx-a11y'], diff --git a/package-lock.json b/package-lock.json index e88315cb4..2bab40225 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8854,9 +8854,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", - "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz", + "integrity": "sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA==" }, "eslint-scope": { "version": "5.0.0", @@ -10750,6 +10750,11 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", + "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==" + }, "gatsby-cli": { "version": "2.11.11", "resolved": "https://registry.npmjs.org/gatsby-cli/-/gatsby-cli-2.11.11.tgz", @@ -19475,6 +19480,11 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" }, + "normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" + }, "npm-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", diff --git a/package.json b/package.json index 968c3c9f8..2ce707688 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@mdx-js/react": "^1.6.4", "classnames": "^2.2.6", "date-fns": "^2.14.0", + "eslint-plugin-react-hooks": "^4.0.4", "gatsby": "^2.20.25", "gatsby-image": "^2.3.4", "gatsby-plugin-google-tagmanager": "^2.3.3", @@ -23,6 +24,7 @@ "gatsby-transformer-remark": "^2.7.3", "gatsby-transformer-sharp": "^2.4.6", "node-sass": "^4.13.1", + "normalize.css": "^8.0.1", "prism-react-renderer": "^1.1.1", "prop-types": "^15.7.2", "react": "^16.12.0", diff --git a/src/components/GuideTile.module.scss b/src/components/GuideTile.module.scss index 5a6b8b2b4..7fb015f33 100644 --- a/src/components/GuideTile.module.scss +++ b/src/components/GuideTile.module.scss @@ -5,7 +5,7 @@ 'main main main'; background-color: white; box-shadow: var(--boxshadow); - padding: 1.5rem; + padding: 0.5rem; button:hover { transform: translateY(-1px); @@ -40,5 +40,6 @@ font-size: 0.9rem; margin-bottom: 1.5rem; max-width: calc(100% - 4rem); - color: var(--color-neutrals-500); + color: var(--color-neutrals-600); + flex: 1; } diff --git a/src/components/IconGallery.js b/src/components/IconGallery.js index 82615b552..0f4197ea7 100644 --- a/src/components/IconGallery.js +++ b/src/components/IconGallery.js @@ -5,12 +5,13 @@ import IconReference from './IconReference'; const IconGallery = () => { if (typeof window === 'undefined') global.window = {}; + const [search, setSearch] = useState(''); + // Get the Icon component when available const { Icon } = window.__NR1_SDK__?.default ?? {}; if (!Icon) return null; // Basic search / filtering - const [search, setSearch] = useState(''); const types = Object.keys(Icon.TYPE); const filterByString = (input) => (str) => str.toLowerCase().includes(input.toLowerCase()); diff --git a/src/components/Layout.js b/src/components/Layout.js index 7cfb1fcbe..baa03e316 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -7,6 +7,7 @@ import GlobalHeader from './GlobalHeader'; import MobileHeader from './MobileHeader'; import Sidebar from './Sidebar'; import styles from './Layout.module.scss'; +import 'normalize.css'; import './styles.scss'; const Layout = ({ children }) => { diff --git a/src/components/Layout.module.scss b/src/components/Layout.module.scss index c4fe3aabe..cb72efcdc 100644 --- a/src/components/Layout.module.scss +++ b/src/components/Layout.module.scss @@ -5,7 +5,6 @@ } .main { - min-height: 100%; display: grid; grid-template-columns: 300px minmax(0, 1fr); width: 100%; diff --git a/src/components/Navigation.js b/src/components/Navigation.js index cdd2a1395..69f466fcd 100644 --- a/src/components/Navigation.js +++ b/src/components/Navigation.js @@ -9,9 +9,13 @@ import matchSearchString from '../utils/matchSearchString'; import styles from './Navigation.module.scss'; +// TODO: Add this implementation +const filterPages = (pages, _searchTerm) => pages; + // recursively create navigation const renderNav = (pages, searches, depthLevel = 0) => { const crumbs = useContext(BreadcrumbContext).flatMap((x) => x.displayName); + const isHomePage = crumbs.length === 0 && depthLevel === 0; const groupedPages = pages.reduce((groups, page) => { const { group = '' } = page; @@ -29,9 +33,7 @@ const renderNav = (pages, searches, depthLevel = 0) => { )} {pages.map((page) => { const [isExpanded, setIsExpanded] = useState( - searches !== undefined || - crumbs.includes(page.displayName) || - crumbs.length === depthLevel + isHomePage || crumbs.includes(page.displayName) ); const isCurrentPage = crumbs[crumbs.length - 1] === page.displayName; @@ -54,7 +56,13 @@ const renderNav = (pages, searches, depthLevel = 0) => { )} > {page.url ? ( - + {page.displayName} {isCurrentPage && ( { onKeyPress={() => setIsExpanded(!isExpanded)} tabIndex={0} > + {depthLevel > 0 && ( + + )} {page.displayName} )} diff --git a/src/components/Navigation.module.scss b/src/components/Navigation.module.scss index f747b3129..6a3b3cbf6 100644 --- a/src/components/Navigation.module.scss +++ b/src/components/Navigation.module.scss @@ -27,6 +27,11 @@ align-items: center; justify-content: space-between; text-decoration: none; + transition: 0.1s; + + &:not(.isCurrentPage):hover { + color: var(--color-neutrals-600); + } [data-depth='0'] > & { font-weight: bold; @@ -50,6 +55,15 @@ button.navLink { stroke-width: 4; } +.nestedChevron { + margin-right: 0.5rem; + stroke-width: 4; + transition: 0.2s; + &.isExpanded { + transform: rotate(90deg); + } +} + .groupName { color: var(--color-neutrals-600); font-weight: bold; diff --git a/src/components/Step.module.scss b/src/components/Step.module.scss index 1591bc1d6..4c6d999f8 100644 --- a/src/components/Step.module.scss +++ b/src/components/Step.module.scss @@ -19,7 +19,7 @@ h6:first-child { color: var(--color-neutrals-900); font-weight: bold; - margin-top: 0.5rem; + margin-top: 0; margin-bottom: 1rem; font-size: 1rem; } diff --git a/src/components/Video.module.scss b/src/components/Video.module.scss index c522733b8..e6cd9b9f4 100644 --- a/src/components/Video.module.scss +++ b/src/components/Video.module.scss @@ -1,6 +1,5 @@ .video { max-width: 560px; - margin: 1rem 0; iframe { width: 100%; diff --git a/src/components/styles.scss b/src/components/styles.scss index 1907c099b..7a07ed788 100644 --- a/src/components/styles.scss +++ b/src/components/styles.scss @@ -57,12 +57,12 @@ --color-teal-800: #003539; --color-teal-900: #002123; - --boxshadow: 0px 2.76726px 2.21381px rgba(0, 85, 90, 0.0168687), - 0px 6.6501px 5.32008px rgba(0, 85, 90, 0.0242336), - 0px 12.5216px 10.0172px rgba(0, 85, 90, 0.03), - 0px 22.3363px 17.869px rgba(0, 85, 90, 0.0357664), - 0px 41.7776px 33.4221px rgba(0, 85, 90, 0.0431313), - 0px 100px 80px rgba(0, 85, 90, 0.06); + --boxshadow: 0 0.24905px 0.55345px rgba(0, 0, 0, 0.00562291), + 0 0.59851px 1.33002px rgba(0, 0, 0, 0.00807786), + 0 1.12694px 2.50431px rgba(0, 0, 0, 0.01), + 0 2.01027px 4.46726px rgba(0, 0, 0, 0.0119221), + 0 3.75998px 8.35552px rgba(0, 0, 0, 0.0143771), + 0 9px 20px rgba(0, 0, 0, 0.02); --color-tile-background: rgb(215, 210, 233); --primary-font-family: 'open sans', sans-serif; @@ -74,19 +74,11 @@ --height-mobile-nav-bar: 60px; } -/*-- Reset --*/ -// https://github.com/necolas/normalize.css -html { - line-height: 1.15; - -webkit-text-size-adjust: 100%; -} - * { box-sizing: border-box; } body { - margin: 0; font-size: 16px; font-family: var(--primary-font-family); color: var(--color-neutrals-700); @@ -94,20 +86,12 @@ body { line-height: 1.5; } -main { - display: block; -} - a { cursor: pointer; text-decoration: none; color: var(--color-brand-800); } -img { - border-style: none; -} - p { margin-top: 0; margin-bottom: 1em; @@ -126,17 +110,6 @@ h6 { font-family: var(--primary-font-family); } -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0; -} - input, select { border: 1px solid var(--color-neutrals-400); @@ -149,7 +122,6 @@ button, [type='button'], [type='reset'], [type='submit'] { - -webkit-appearance: button; display: inline-flex; align-items: center; justify-content: center; @@ -177,6 +149,10 @@ code { font-family: var(--code-font); } +pre { + margin: 0; +} + :global { .intro-text { color: var(--color-neutrals-600); @@ -186,6 +162,6 @@ code { .site-container { max-width: 1460px; - margin: auto; + margin: 0 auto; } } diff --git a/src/data/sidenav.json b/src/data/sidenav.json index 1e5ff10d3..388831b20 100644 --- a/src/data/sidenav.json +++ b/src/data/sidenav.json @@ -8,15 +8,15 @@ "url": "/" }, { - "displayName": "OpenTelemetry Exporter", + "displayName": "OpenTelemetry exporter", "url": "/" }, { - "displayName": "Extend New Relic Agents", + "displayName": "Extend New Relic agents", "url": "/" }, { - "displayName": "Create Flex Integration", + "displayName": "Create a Flex integration", "url": "/" } ] @@ -26,41 +26,40 @@ "url": "/explore-data", "children": [ { - "displayName": "Write NRQL Queries", + "displayName": "Write NRQL queries", "url": "/" }, { - "displayName": "Build a NerdGraph Query", + "displayName": "Build a NerdGraph query", "url": "/" } ] }, { "displayName": "Build apps", - "url": "/build-apps", "children": [ { - "displayName": "Map Pageviews by Region", + "displayName": "Map pageviews by region", "url": "/build-apps/map-pageviews-by-region" }, { - "displayName": "Optimize Cloud Usage", + "displayName": "Optimize cloud usage", "url": "/build-apps/optimize-cloud-usage" }, { - "displayName": "Add a Time Picker", + "displayName": "Add a time picker", "url": "/build-apps/add-time-picker-guide" }, { - "displayName": "Use NerdGraph in an App", + "displayName": "Use NerdGraph in an app", "url": "/build-apps/use-nerdgraph-in-app" }, { - "displayName": "Build a Table", + "displayName": "Build a table", "url": "/build-apps/build-a-table" }, { - "displayName": "Persistent Storage for Apps", + "displayName": "Persistent storage for apps", "url": "/build-apps/persistent-storage-for-apps" } ] @@ -70,11 +69,11 @@ "url": "/automate-workflows", "children": [ { - "displayName": "Monitor, Alert, and Analyze", + "displayName": "Monitor, alert, and analyze", "url": "/automate-workflows/" }, { - "displayName": "OpenTelemetry Exporter", + "displayName": "OpenTelemetry exporter", "url": "/automate-workflows/" } ] diff --git a/src/hooks/useApiDoc.js b/src/hooks/useApiDoc.js index 74f9b8ef1..634e3a24b 100644 --- a/src/hooks/useApiDoc.js +++ b/src/hooks/useApiDoc.js @@ -86,7 +86,7 @@ const useApiDoc = (name) => { }; }), }; - }, [name, window.__NR1_SDK__]); + }, [name]); }; export default useApiDoc; diff --git a/src/hooks/useComponentDoc.js b/src/hooks/useComponentDoc.js index 926d964bf..5f53a6aa9 100644 --- a/src/hooks/useComponentDoc.js +++ b/src/hooks/useComponentDoc.js @@ -80,7 +80,7 @@ const useComponentDoc = (componentName) => { tagsFromComponentProperties.concat(tagsFromPropTypes) ), }; - }, [componentName, window?.__NR1_SDK__]); + }, [componentName]); }; export default useComponentDoc; diff --git a/src/markdown-pages/automate-workflows/workflow-automation.mdx b/src/markdown-pages/automate-workflows/workflow-automation.mdx new file mode 100644 index 000000000..6dfbf619a --- /dev/null +++ b/src/markdown-pages/automate-workflows/workflow-automation.mdx @@ -0,0 +1,84 @@ +--- +path: '/automate-workflows/workflow-automation' +title: 'Orchestrate observability workflows' +description: 'Tools to automate your observability ecosystem' +template: 'GuideTemplate' +--- + +## Define and automate observability + +As the maintainer of an increasingly complex software stack, you need to +pinpoint problems without filtering through all the noise. You need to automate +observability the way you automate infrastructure management, by embedding your +New Relic configuration in code. The [Terraform +Provider](https://www.terraform.io/docs/providers/newrelic/index.html) enables +observability as code -- the ability to monitor, alert, and analyze your +ecosystem in one place, in real time. Built on the New Relic Client, the +Terraform Provider provides a full implementation of APIs that enable you to +create alert policies and conditions, Synthetic monitors and Synthetics alert +conditions, notification policies and more. + +The [Terraform Provider getting started +guide](https://www.terraform.io/docs/providers/newrelic/guides/getting_started.html) +steps you through some fundamental configuration. The [New Relic APM Terraform +module](https://registry.terraform.io/modules/newrelic/apm/newrelic/0.0.4) +provides a monitoring strategy for application resources reporting into New +Relic. + +