From 2f23feed229149ffd339e6eb06d309f72f7d5163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw?= Date: Mon, 20 Jul 2020 13:24:40 +0200 Subject: [PATCH] feat: adjusting main menu functionalities --- .github/workflows/pr.yml | 2 +- .prettierrc.js | 8 + .prettierrc.yaml | 7 - README.md | 6 +- commitlint.config.js | 6 +- jest.config.js | 24 +- .../network-telemetry-overview/MainMenu.jsx | 36 ++- .../network-telemetry-overview/common.jsx | 8 +- .../network-telemetry-overview/constants.js | 64 ++--- nerdlets/network-telemetry-overview/fetch.js | 12 +- nerdlets/network-telemetry-overview/index.jsx | 4 +- .../network-telemetry-overview/ip-address.jsx | 21 +- .../ipfix-detail.jsx | 34 +-- nerdlets/network-telemetry-overview/ipfix.jsx | 83 +++--- .../network-summary.jsx | 32 +-- .../network-telemetry-nerdlet.jsx | 141 +++++------ nerdlets/network-telemetry-overview/sflow.jsx | 44 ++-- .../network-telemetry-overview/styles.scss | 4 +- package-lock.json | 236 +++++++++++++++++- package.json | 6 +- src/components/account-dropdown/index.jsx | 49 ++-- src/components/time-range/index.js | 4 +- src/lib/accounts-with-data.js | 2 +- src/lib/bytes-to-size.js | 24 +- src/lib/find-related-account-with.js | 6 +- src/lib/nrdb-query.js | 10 +- src/lib/nrql.js | 2 +- test/test.test.js | 2 +- test/utils/setupTests.jsx | 4 +- 29 files changed, 576 insertions(+), 305 deletions(-) create mode 100644 .prettierrc.js delete mode 100644 .prettierrc.yaml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 1b89ed9..38b25d8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -73,7 +73,7 @@ jobs: - name: Run eslint-check and generate report id: eslint-check run: | - npm run eslint-check -- --output-file eslint_report.json --format json + npm run eslint-check continue-on-error: true - name: Annotate Lint Results diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..56448cf --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,8 @@ +module.exports = { + printWidth: 100, + trailingComma: 'es5', + tabWidth: 2, + singleQuote: true, + useTabs: false, + jsxSingleQuote: true, +}; diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index c792ecb..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# .prettierrc -printWidth: 100 -tabWidth: 2 -useTabs: false -jsxSingleQuote: true -trailingComma: "es5" - diff --git a/README.md b/README.md index 7d07b01..582c9a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![New Relic One Catalog Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/New_Relic_One_Catalog_Project.png)](https://opensource.newrelic.com/oss-category/#new-relic-one-catalog-project) -# New Relic One Network Telemetry (nr1-network-telemetry) +# NR1 Network Telemetry (nr1-network-telemetry) ![CI](https://github.com/newrelic/nr1-network-telemetry/workflows/CI/badge.svg) ![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/newrelic/nr1-network-telemetry?include_prereleases&sort=semver) [![Snyk](https://snyk.io/test/github/newrelic/nr1-network-telemetry/badge.svg)](https://snyk.io/test/github/newrelic/nr1-network-telemetry) @@ -42,7 +42,9 @@ Next, clone this repository and run the following scripts: ```bash nr1 nerdpack:clone -r https://github.com/newrelic/nr1-network-telemetry.git cd nr1-network-telemetry -nr1 nerdpack:serve +nr1 nerdpack:uuid -gf +npm install +npm start ``` Visit [https://one.newrelic.com/?nerdpacks=local](https://one.newrelic.com/?nerdpacks=local), navigate to the Nerdpack, and :sparkles: diff --git a/commitlint.config.js b/commitlint.config.js index 567b002..4d93b8e 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,7 +1,7 @@ module.exports = { - extends: ["@commitlint/config-conventional"], + extends: ['@commitlint/config-conventional'], rules: { - "scope-case": [0], - "subject-case": [0], + 'scope-case': [0], + 'subject-case': [0], }, }; diff --git a/jest.config.js b/jest.config.js index c1ef0a1..6cb1a17 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,19 +1,19 @@ module.exports = { - collectCoverageFrom: ["**/*.js?(x)"], - coveragePathIgnorePatterns: ["index.js", "test"], + collectCoverageFrom: ['**/*.js?(x)'], + coveragePathIgnorePatterns: ['index.js', 'test'], moduleNameMapper: { - "\\.(scss|css)$": "/test/utils/mocks/styleMock.jsx", - "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": - "identity-obj-proxy", - "^shared/(.*)$": "/lib/$1", - "^testUtils/(.+)$": "/test/utils/$1", + '\\.(scss|css)$': '/test/utils/mocks/styleMock.jsx', + '^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + 'identity-obj-proxy', + '^shared/(.*)$': '/lib/$1', + '^testUtils/(.+)$': '/test/utils/$1', }, // Ignore npm caching to avoid problems with jest and chalk throwing errors // when running in grand central. Fix grabbed from this github issue: // https://github.com/facebook/jest/issues/4682#issuecomment-352899677 - modulePathIgnorePatterns: ["npm-cache", ".npm"], - rootDir: ".", - setupFiles: ["/test/utils/setupTests.jsx"], - testMatch: ["/**/*.test.js?(x)"], - testPathIgnorePatterns: ["/node_modules/", "/__mocks__/", ".eslintrc.js"], + modulePathIgnorePatterns: ['npm-cache', '.npm'], + rootDir: '.', + setupFiles: ['/test/utils/setupTests.jsx'], + testMatch: ['/**/*.test.js?(x)'], + testPathIgnorePatterns: ['/node_modules/', '/__mocks__/', '.eslintrc.js'], }; diff --git a/nerdlets/network-telemetry-overview/MainMenu.jsx b/nerdlets/network-telemetry-overview/MainMenu.jsx index 532da59..6edfba8 100644 --- a/nerdlets/network-telemetry-overview/MainMenu.jsx +++ b/nerdlets/network-telemetry-overview/MainMenu.jsx @@ -1,16 +1,18 @@ -import { BlockText, Checkbox, Radio, RadioGroup, nerdlet } from "nr1"; -import { AccountDropdown } from "../../src/components/account-dropdown"; +import { BlockText, Checkbox, Radio, RadioGroup, nerdlet } from 'nr1'; +import { AccountDropdown } from '../../src/components/account-dropdown'; import { DATA_SOURCES, INTERVAL_SECONDS_DEFAULT, INTERVAL_SECONDS_MAX, INTERVAL_SECONDS_MIN, NRQL_QUERY_LIMIT_DEFAULT, -} from "./constants"; -import InputRange from "react-input-range"; -import PropTypes from "prop-types"; -import React from "react"; -import debounce from "lodash/debounce"; + NRQL_QUERY_LIMIT_MAX, + NRQL_QUERY_LIMIT_MIN, +} from './constants'; +import InputRange from 'react-input-range'; +import PropTypes from 'prop-types'; +import React from 'react'; +import debounce from 'lodash/debounce'; export default class MainMenu extends React.Component { static propTypes = { @@ -33,10 +35,7 @@ export default class MainMenu extends React.Component { handleDataSourceChange = value => { const dataSource = parseInt(value, 10); - - if (dataSource >= 0) { - nerdlet.setUrlState({ dataSource }); - } + nerdlet.setUrlState({ dataSource }); }; handleIntervalSecondsChange = debounce(value => { @@ -53,6 +52,20 @@ export default class MainMenu extends React.Component { } } + handleLimitChange(evt, value) { + const queryLimit = parseInt(value, 10); + + if (queryLimit >= NRQL_QUERY_LIMIT_MIN && queryLimit <= NRQL_QUERY_LIMIT_MAX) { + nerdlet.setUrlState({ queryLimit }); + } + } + + handleHideLabelsChange(evt) { + const hideLabels = ((evt || {}).target || {}).checked || false; + + nerdlet.setUrlState({ hideLabels }); + } + render() { const dataSource = this.props.nerdletUrlState.dataSource || 0; const queryLimit = this.props.nerdletUrlState.queryLimit || NRQL_QUERY_LIMIT_DEFAULT; @@ -67,6 +80,7 @@ export default class MainMenu extends React.Component { diff --git a/nerdlets/network-telemetry-overview/common.jsx b/nerdlets/network-telemetry-overview/common.jsx index 6bdee02..fab3e4d 100644 --- a/nerdlets/network-telemetry-overview/common.jsx +++ b/nerdlets/network-telemetry-overview/common.jsx @@ -1,14 +1,14 @@ -import { BlockText, Icon } from "nr1"; -import React from "react"; +import { BlockText, Icon } from 'nr1'; +import React from 'react'; export const renderDeviceHeader = (deviceName, deviceType) => { return (
- {deviceName || "All Devices"} + {deviceName || 'All Devices'} - {deviceType || "Device"} + {deviceType || 'Device'}
diff --git a/nerdlets/network-telemetry-overview/constants.js b/nerdlets/network-telemetry-overview/constants.js index 8ab66d5..f279a44 100644 --- a/nerdlets/network-telemetry-overview/constants.js +++ b/nerdlets/network-telemetry-overview/constants.js @@ -1,5 +1,5 @@ -import Ipfix from "./ipfix"; -import Sflow from "./sflow"; +import Ipfix from './ipfix'; +import Sflow from './sflow'; /* * Constants @@ -12,32 +12,32 @@ export const INTERVAL_SECONDS_DEFAULT = 30; export const BLURRED_LINK_OPACITY = 0.3; export const FOCUSED_LINK_OPACITY = 0.8; -export const COLOR_END = "#FFC400"; -export const COLOR_START = "#3ED2F2"; +export const COLOR_END = '#FFC400'; +export const COLOR_START = '#3ED2F2'; export const COLORS = [ - "#11A893", - "#00B3D7", - "#FFC400", - "#A45AC1", - "#83CB4E", - "#FA6E37", - "#C40685", - - "#4ACAB7", - "#3ED2F2", - "#FFDD78", - "#C07DDB", - "#A2E572", - "#FF9269", - "#E550B0", - - "#0E7365", - "#0189A4", - "#CE9E00", - "#79428E", - "#63973A", - "#C6562C", - "#910662", + '#11A893', + '#00B3D7', + '#FFC400', + '#A45AC1', + '#83CB4E', + '#FA6E37', + '#C40685', + + '#4ACAB7', + '#3ED2F2', + '#FFDD78', + '#C07DDB', + '#A2E572', + '#FF9269', + '#E550B0', + + '#0E7365', + '#0189A4', + '#CE9E00', + '#79428E', + '#63973A', + '#C6562C', + '#910662', ]; export const NRQL_QUERY_LIMIT_DEFAULT = 50; @@ -45,11 +45,11 @@ export const NRQL_QUERY_LIMIT_MIN = 1; export const NRQL_QUERY_LIMIT_MAX = 100; export const NRQL_IPFIX_WHERE_NO_PRIVATE_ASN = - " WHERE (bgpSourceAsNumber > 1 AND bgpSourceAsNumber < 64495)" + - " OR (bgpSourceAsNumber > 65534 AND bgpSourceAsNumber < 4200000000)" + - " OR (bgpSourceAsNumber > 4294967294)"; + ' WHERE (bgpSourceAsNumber > 1 AND bgpSourceAsNumber < 64495)' + + ' OR (bgpSourceAsNumber > 65534 AND bgpSourceAsNumber < 4200000000)' + + ' OR (bgpSourceAsNumber > 4294967294)'; export const DATA_SOURCES = [ - { component: Ipfix, eventType: "ipfix", name: "ipfix" }, - { component: Sflow, eventType: "sflow", name: "sflow" }, + { component: Ipfix, eventType: 'ipfix', name: 'ipfix' }, + { component: Sflow, eventType: 'sflow', name: 'sflow' }, ]; diff --git a/nerdlets/network-telemetry-overview/fetch.js b/nerdlets/network-telemetry-overview/fetch.js index db9db97..3886871 100644 --- a/nerdlets/network-telemetry-overview/fetch.js +++ b/nerdlets/network-telemetry-overview/fetch.js @@ -1,5 +1,5 @@ -import { COLORS } from "./constants"; -import { fetchNrqlResults } from "../../src/lib/nrql"; +import { COLORS } from './constants'; +import { fetchNrqlResults } from '../../src/lib/nrql'; /* * Fetch some node /link stuff via NRQL @@ -17,7 +17,13 @@ export const fetchRelationshipFacets = async (accountId, nrqlQuery) => { const ids = (row.facet || []).map(f => { const id = nodes.findIndex(node => node.name === f); if (id < 0) - return nodes.push({ color: COLORS[nodes.length % COLORS.length], name: f, value: 0 }) - 1; + return ( + nodes.push({ + color: COLORS[nodes.length % COLORS.length], + name: f, + value: 0, + }) - 1 + ); return id; }); diff --git a/nerdlets/network-telemetry-overview/index.jsx b/nerdlets/network-telemetry-overview/index.jsx index dbbeeb2..c307c9e 100644 --- a/nerdlets/network-telemetry-overview/index.jsx +++ b/nerdlets/network-telemetry-overview/index.jsx @@ -1,5 +1,5 @@ -import NetworkTelemetryNerdlet from "./network-telemetry-nerdlet"; -import React from "react"; +import NetworkTelemetryNerdlet from './network-telemetry-nerdlet'; +import React from 'react'; export default class Wrapper extends React.PureComponent { render() { diff --git a/nerdlets/network-telemetry-overview/ip-address.jsx b/nerdlets/network-telemetry-overview/ip-address.jsx index 542bb98..7dcd873 100644 --- a/nerdlets/network-telemetry-overview/ip-address.jsx +++ b/nerdlets/network-telemetry-overview/ip-address.jsx @@ -1,9 +1,9 @@ -import { Toast, navigation } from "nr1"; -import PropTypes from "prop-types"; -import React from "react"; +import { Toast, navigation } from 'nr1'; +import PropTypes from 'prop-types'; +import React from 'react'; -import findRelatedAccountsWith from "../../src/lib/find-related-account-with"; -import nrdbQuery from "../../src/lib/nrdb-query"; +import findRelatedAccountsWith from '../../src/lib/find-related-account-with'; +import nrdbQuery from '../../src/lib/nrdb-query'; /** * render an ip address that on click will attempt to find the @@ -30,11 +30,14 @@ export default class IpAddress extends React.Component { const where = `ipV4Address LIKE '${ipAddress}/%'`; const notFoundToast = { description: `Could not find ${ipAddress} on any monitored hosts.`, - title: "IP Address Not Found", + title: 'IP Address Not Found', type: Toast.TYPE.NORMAL, }; - const accounts = await findRelatedAccountsWith({ eventType: "NetworkSample", where }); + const accounts = await findRelatedAccountsWith({ + eventType: 'NetworkSample', + where, + }); if (accounts.length === 0) { Toast.showToast(notFoundToast); } else { @@ -52,7 +55,7 @@ export default class IpAddress extends React.Component { render() { const { searching } = this.state || {}; - const className = searching ? "ip-address-searching" : "ip-address"; + const className = searching ? 'ip-address-searching' : 'ip-address'; return (
- {this.props.value || "(unknown)"} + {this.props.value || '(unknown)'}
); } diff --git a/nerdlets/network-telemetry-overview/ipfix-detail.jsx b/nerdlets/network-telemetry-overview/ipfix-detail.jsx index 5b0db7c..e29154c 100644 --- a/nerdlets/network-telemetry-overview/ipfix-detail.jsx +++ b/nerdlets/network-telemetry-overview/ipfix-detail.jsx @@ -1,8 +1,8 @@ -import { ChartGroup, HeadingText, LineChart, SparklineChart } from "nr1"; +import { ChartGroup, HeadingText, LineChart, SparklineChart } from 'nr1'; -import PropTypes from "prop-types"; -import React from "react"; -import { renderDeviceHeader } from "./common"; +import PropTypes from 'prop-types'; +import React from 'react'; +import { renderDeviceHeader } from './common'; export default class IpfixDetail extends React.Component { static propTypes = { @@ -18,39 +18,39 @@ export default class IpfixDetail extends React.Component { render() { const { accountId, filter, hideLabels, name } = this.props; - const displayName = hideLabels ? "(redacted)" : name; + const displayName = hideLabels ? '(redacted)' : name; // Kill facets when labels are hidden const ChartComponent = hideLabels ? SparklineChart : LineChart; const throughputQuery = - "FROM ipfix" + + 'FROM ipfix' + " SELECT sum(octetDeltaCount * 64000) as 'throughput'" + - (filter || "") + - " TIMESERIES"; + (filter || '') + + ' TIMESERIES'; const destQuery = - "FROM ipfix" + + 'FROM ipfix' + " SELECT sum(octetDeltaCount * 64000) as 'throughput'" + - (filter || "") + - " FACET destinationIPv4Address " + - " TIMESERIES"; + (filter || '') + + ' FACET destinationIPv4Address ' + + ' TIMESERIES'; const protocolQuery = - "FROM ipfix" + + 'FROM ipfix' + " SELECT count(*) as 'flows'" + - (filter || "") + - " FACET cases(" + + (filter || '') + + ' FACET cases(' + " WHERE protocolIdentifier = 1 as 'ICMP', " + " WHERE protocolIdentifier = 6 as 'TCP'," + " WHERE protocolIdentifier = 17 as 'UDP'," + " WHERE protocolIdentifier IS NOT NULL as 'other')" + - " TIMESERIES"; + ' TIMESERIES'; return (
- {renderDeviceHeader(displayName, "Network Entity")} + {renderDeviceHeader(displayName, 'Network Entity')} Total Throughput = INTERVAL_SECONDS_MIN) { - // Fire right away, then schedule this.fetchIpfixData(); this.refresh = setInterval(async () => { @@ -123,9 +119,6 @@ export default class Ipfix extends React.Component { this.startTimer(); } - /* - * Helper functions - */ handleSankeyLinkClick(detailData, evt) { this.setState({ detailData, detailHidden: false }); } @@ -153,16 +146,16 @@ export default class Ipfix extends React.Component { const { filterPrivateAsns, peerBy } = this.state; return ( - "FROM ipfix" + + 'FROM ipfix' + " SELECT sum(octetDeltaCount * 64000) as 'value'" + - (filterPrivateAsns ? NRQL_IPFIX_WHERE_NO_PRIVATE_ASN : "") + - " FACET " + + (filterPrivateAsns ? NRQL_IPFIX_WHERE_NO_PRIVATE_ASN : '') + + ' FACET ' + peerBy + - ", agent, destinationIPv4Address" + - " SINCE " + + ', agent, destinationIPv4Address' + + ' SINCE ' + intervalSeconds + - " seconds ago" + - " LIMIT " + + ' seconds ago' + + ' LIMIT ' + queryLimit ); } @@ -192,10 +185,10 @@ export default class Ipfix extends React.Component { if (!account || !detailData || detailHidden) return; - let peerName = (nodes[detailData.sourceId] || {}).name || ""; + let peerName = (nodes[detailData.sourceId] || {}).name || ''; let filter = - (filterPrivateAsns ? `${NRQL_IPFIX_WHERE_NO_PRIVATE_ASN} AND ` : "WHERE ") + peerBy + " = "; - if (peerBy === "bgpSourceAsNumber") { + (filterPrivateAsns ? `${NRQL_IPFIX_WHERE_NO_PRIVATE_ASN} AND ` : 'WHERE ') + peerBy + ' = '; + if (peerBy === 'bgpSourceAsNumber') { filter += peerName; } else { filter += "'" + peerName + "'"; @@ -203,7 +196,7 @@ export default class Ipfix extends React.Component { if ((detailData.source || {}).depth === 1) { filter += " AND destinationIPv4Address = '" + detailData.target.name + "'"; - peerName += " to " + detailData.target.name; + peerName += ' to ' + detailData.target.name; } return ( @@ -313,14 +306,38 @@ export default class Ipfix extends React.Component {
No results found
) : (
-
-
+
+
Source
-
+
Router
-
+
Destination
@@ -342,8 +359,8 @@ export default class Ipfix extends React.Component { diff --git a/nerdlets/network-telemetry-overview/network-summary.jsx b/nerdlets/network-telemetry-overview/network-summary.jsx index 0b626b5..cb5acd1 100644 --- a/nerdlets/network-telemetry-overview/network-summary.jsx +++ b/nerdlets/network-telemetry-overview/network-summary.jsx @@ -1,10 +1,10 @@ -import { bitsToSize, intToSize } from "../../src/lib/bytes-to-size"; +import { bitsToSize, intToSize } from '../../src/lib/bytes-to-size'; -import IpAddress from "./ip-address"; -import PropTypes from "prop-types"; -import React from "react"; -import { Table } from "semantic-ui-react"; -import { renderDeviceHeader } from "./common"; +import IpAddress from './ip-address'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { Table } from 'semantic-ui-react'; +import { renderDeviceHeader } from './common'; export default class NetworkSummary extends React.Component { static propTypes = { @@ -18,14 +18,14 @@ export default class NetworkSummary extends React.Component { static defaultProps = { columns: [ - { align: "center", data: "color", label: null }, - { align: "left", data: "name", label: "Source" }, - { align: "right", data: "value", label: "Throughput" }, + { align: 'center', data: 'color', label: null }, + { align: 'left', data: 'name', label: 'Source' }, + { align: 'right', data: 'value', label: 'Throughput' }, ], data: [], - deviceName: "All Devices", - deviceType: "Device", - height: "100%", + deviceName: 'All Devices', + deviceType: 'Device', + height: '100%', hideLabels: false, selectedNodeId: null, }; @@ -44,16 +44,16 @@ export default class NetworkSummary extends React.Component { {columns.map((c, i) => { switch (c.data) { - case "color": + case 'color': return (
); - case "value": + case 'value': return ( - {c.type && c.type === "count" + {c.type && c.type === 'count' ? intToSize(node.value, hideLabels) : bitsToSize(node.value, hideLabels)} @@ -80,7 +80,7 @@ export default class NetworkSummary extends React.Component { {columns.map((c, i) => ( - {c.label || " "} + {c.label || ' '} ))} diff --git a/nerdlets/network-telemetry-overview/network-telemetry-nerdlet.jsx b/nerdlets/network-telemetry-overview/network-telemetry-nerdlet.jsx index f284f13..8a647bc 100644 --- a/nerdlets/network-telemetry-overview/network-telemetry-nerdlet.jsx +++ b/nerdlets/network-telemetry-overview/network-telemetry-nerdlet.jsx @@ -6,18 +6,12 @@ import { NerdletStateContext, PlatformStateContext, Spinner, - nerdlet, -} from "nr1"; -import { - DATA_SOURCES, - INTERVAL_SECONDS_DEFAULT, - NRQL_QUERY_LIMIT_DEFAULT, - NRQL_QUERY_LIMIT_MAX, - NRQL_QUERY_LIMIT_MIN, -} from "./constants"; -import MainMenu from "./MainMenu"; +} from 'nr1'; +import { EmptyState } from '@newrelic/nr1-community'; +import { DATA_SOURCES, INTERVAL_SECONDS_DEFAULT, NRQL_QUERY_LIMIT_DEFAULT } from './constants'; +import MainMenu from './MainMenu'; -import React from "react"; +import React from 'react'; export default class NetworkTelemetryNerdlet extends React.Component { constructor(props) { @@ -25,26 +19,16 @@ export default class NetworkTelemetryNerdlet extends React.Component { this.state = { account: {}, + accountsList: [], isLoading: true, }; - - this.handleLimitChange = this.handleLimitChange.bind(this); - this.handleHideLabelsChange = this.handleHideLabelsChange.bind(this); } - handleLimitChange(evt, value) { - const queryLimit = parseInt(value, 10); - - if (queryLimit >= NRQL_QUERY_LIMIT_MIN && queryLimit <= NRQL_QUERY_LIMIT_MAX) { - nerdlet.setUrlState({ queryLimit }); + handleAccountsLoaded = accountsList => { + if (accountsList && accountsList.length < 0) { + this.setState({ accountsList }); } - } - - handleHideLabelsChange(evt) { - const hideLabels = ((evt || {}).target || {}).checked || false; - - nerdlet.setUrlState({ hideLabels }); - } + }; accountFilter(account) { return DATA_SOURCES.reduce((found, source) => { @@ -52,8 +36,64 @@ export default class NetworkTelemetryNerdlet extends React.Component { }, false); } + renderSelectAccountAlert = () => { + return ( +
+ + + Please Select an Account + +
+ ); + }; + + renderDSComponent = () => { + const { account } = this.state; + return ( + + {platformUrlState => { + const { timeRange } = platformUrlState; + return ( + + {nerdletUrlState => { + const { + dataSource = 0, + hideLabels = false, + intervalSeconds, + queryLimit, + } = nerdletUrlState; + const DsComponent = (DATA_SOURCES[dataSource] || {}).component; + return ( + + ); + }} + + ); + }} + + ); + }; + + renderAccountsListError = () => { + return ( + + ); + }; + render() { - const { account, isLoading } = this.state; + const { account, isLoading, accountsList } = this.state; return (
@@ -70,49 +110,10 @@ export default class NetworkTelemetryNerdlet extends React.Component {
- {isLoading ? ( - !account.id ? ( -
- - - Please Select an Account - -
- ) : ( - - ) - ) : ( - - {platformUrlState => { - const { timeRange } = platformUrlState; - return ( - - {nerdletUrlState => { - const { - dataSource = 0, - hideLabels = false, - intervalSeconds, - queryLimit, - } = nerdletUrlState; - const DsComponent = (DATA_SOURCES[dataSource] || {}).component; - return ( - - ); - }} - - ); - }} - - )} + {isLoading && account.id && } + {!account.id && accountsList.length > 0 && this.renderSelectAccountAlert()} + {!account.id && accountsList.length === 0 && this.renderAccountsListError()} + {!isLoading && account.id && this.renderDSComponent()}
diff --git a/nerdlets/network-telemetry-overview/sflow.jsx b/nerdlets/network-telemetry-overview/sflow.jsx index 9bb298a..069cce2 100644 --- a/nerdlets/network-telemetry-overview/sflow.jsx +++ b/nerdlets/network-telemetry-overview/sflow.jsx @@ -5,15 +5,15 @@ import { INTERVAL_SECONDS_MIN, NRQL_QUERY_LIMIT_DEFAULT, SUB_MENU_HEIGHT, -} from "./constants"; -import { BlockText, Grid, GridItem, Radio, RadioGroup, Spinner, Stack, StackItem } from "nr1"; +} from './constants'; +import { BlockText, Grid, GridItem, Radio, RadioGroup, Spinner, Stack, StackItem } from 'nr1'; -import ChordDiagram from "react-chord-diagram"; -import NetworkSummary from "./network-summary"; -import PropTypes from "prop-types"; -import React from "react"; -import { fetchRelationshipFacets } from "./fetch"; -import { timeRangeToNrql } from "../../src/components/time-range"; +import ChordDiagram from 'react-chord-diagram'; +import NetworkSummary from './network-summary'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { fetchRelationshipFacets } from './fetch'; +import { timeRangeToNrql } from '../../src/components/time-range'; export default class Sflow extends React.Component { static propTypes = { @@ -44,7 +44,7 @@ export default class Sflow extends React.Component { isLoading: true, links: [], nodes: [], - queryAttribute: "throughput", + queryAttribute: 'throughput', selectedSourceId: -1, width: width, }; @@ -127,26 +127,26 @@ export default class Sflow extends React.Component { const { queryLimit } = this.props; const { queryAttribute } = this.state; - let attr = "sum(scaledByteCount * 8)"; - if (queryAttribute === "count") { - attr = "count(*)"; + let attr = 'sum(scaledByteCount * 8)'; + if (queryAttribute === 'count') { + attr = 'count(*)'; } return ( - "FROM sflow" + - " SELECT " + + 'FROM sflow' + + ' SELECT ' + attr + " as 'value'" + - " FACET networkSourceAddress, networkDestinationAddress" + - " LIMIT " + + ' FACET networkSourceAddress, networkDestinationAddress' + + ' LIMIT ' + queryLimit + - " " + + ' ' + timeRangeToNrql(this.props.timeRange) ); } handleAttributeChange(evt, attr) { - if (attr === "count" || attr === "throughput") { + if (attr === 'count' || attr === 'throughput') { this.setState({ queryAttribute: attr }); } } @@ -201,10 +201,10 @@ export default class Sflow extends React.Component { links.forEach(link => (matrix[link.source][link.target] = link.value)); const summaryColumns = [ - { align: "center", data: "color", label: null }, - { align: "left", data: "source", label: "source" }, - { align: "left", data: "target", label: "target" }, - { align: "right", data: "value", label: queryAttribute }, + { align: 'center', data: 'color', label: null }, + { align: 'left', data: 'source', label: 'source' }, + { align: 'left', data: 'target', label: 'target' }, + { align: 'right', data: 'value', label: queryAttribute }, ]; const summaryData = links diff --git a/nerdlets/network-telemetry-overview/styles.scss b/nerdlets/network-telemetry-overview/styles.scss index 24dfee3..9ecef07 100644 --- a/nerdlets/network-telemetry-overview/styles.scss +++ b/nerdlets/network-telemetry-overview/styles.scss @@ -1,5 +1,5 @@ -@import url("//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css"); -@import "~react-input-range/lib/css/index.css"; +@import url('//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css'); +@import '~react-input-range/lib/css/index.css'; /* Interval Slider */ .interval-range { diff --git a/package-lock.json b/package-lock.json index 1c85580..671fd4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nr1-network-telemetry-nerdpack", - "version": "0.4.0", + "version": "0.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1743,6 +1743,18 @@ "rimraf": "^2.5.2" } }, + "@newrelic/nr1-community": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@newrelic/nr1-community/-/nr1-community-1.2.0.tgz", + "integrity": "sha512-lYPJRxoUjqIEVlSft/w3qmp4iSODKx8twDkVEYiFnSWVZUtfy24dK7nVKijBP8GXLP4pwGz7cGvGNzdaSje2MA==", + "requires": { + "crypto-random-string": "^3.2.0", + "date-fns": "^2.13.0", + "funnel-graph-js": "^1.4.2", + "lodash.get": "^4.4.2", + "nice-color-palettes": "^3.0.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -1814,6 +1826,11 @@ "prop-types": "^15.6.2" } }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, "@stardust-ui/react-component-event-listener": { "version": "0.38.0", "resolved": "https://registry.npmjs.org/@stardust-ui/react-component-event-listener/-/react-component-event-listener-0.38.0.tgz", @@ -1833,6 +1850,14 @@ "react-is": "^16.6.3" } }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, "@types/babel__core": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", @@ -3105,6 +3130,27 @@ "unset-value": "^1.0.0" } }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -3423,6 +3469,14 @@ "is-regexp": "^2.0.0" } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3639,6 +3693,14 @@ "which": "^2.0.1" } }, + "crypto-random-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.2.0.tgz", + "integrity": "sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ==", + "requires": { + "type-fest": "^0.8.1" + } + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -4014,6 +4076,11 @@ } } }, + "date-fns": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.14.0.tgz", + "integrity": "sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -4053,6 +4120,14 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4072,6 +4147,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -4244,6 +4324,11 @@ "is-obj": "^1.0.0" } }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -4270,7 +4355,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -6039,6 +6123,11 @@ "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==", "dev": true }, + "funnel-graph-js": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/funnel-graph-js/-/funnel-graph-js-1.4.2.tgz", + "integrity": "sha512-9bnmcBve7RDH9dTF9BLuUpuisKkDka3yrfhs+Z/106ZgJvqIse1RfKQWjW+QdAlTrZqC9oafen7t/KuJKv9ohA==" + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -6061,7 +6150,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -6203,6 +6291,34 @@ "minimist": "^1.2.5" } }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -6400,6 +6516,11 @@ } } }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -8235,6 +8356,11 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -8313,6 +8439,14 @@ "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz", "integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ==" }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8419,6 +8553,11 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -8513,6 +8652,11 @@ "signal-exit": "^3.0.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -8552,6 +8696,24 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha1-63lhAxwPDo0AG/LVb6toXViCLzg=", + "requires": { + "once": "~1.3.0" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "requires": { + "wrappy": "1" + } + } + } + }, "map-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", @@ -8666,6 +8828,11 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -8692,8 +8859,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "3.0.2", @@ -8798,6 +8964,22 @@ "semver": "^5.4.1" } }, + "new-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/new-array/-/new-array-1.0.0.tgz", + "integrity": "sha1-XbxjnZYerH8an7wacUbsEvKST78=" + }, + "nice-color-palettes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/nice-color-palettes/-/nice-color-palettes-3.0.0.tgz", + "integrity": "sha512-lL4AjabAAFi313tjrtmgm/bxCRzp4l3vCshojfV/ij3IPdtnRqv6Chcw+SqJUhbe7g3o3BecaqCJYUNLswGBhQ==", + "requires": { + "got": "^9.2.2", + "map-limit": "0.0.1", + "minimist": "^1.2.0", + "new-array": "^1.0.0" + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -8879,6 +9061,11 @@ "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", "dev": true }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, "npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -9119,7 +9306,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -9165,6 +9351,11 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, "p-each-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", @@ -9532,6 +9723,11 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, "prettier": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", @@ -9631,7 +9827,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10195,6 +10390,14 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -11747,6 +11950,11 @@ } } }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", @@ -11926,8 +12134,7 @@ "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "typed-styles": { "version": "0.0.7", @@ -12133,6 +12340,14 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -12374,8 +12589,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index 36ffb83..dc7c53f 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,13 @@ "version": "0.4.1", "scripts": { "checkversion": "check-node-version --node $(cat .nvmrc)", - "format": "prettier \"**/*.js\" \"**/*.json\" \"**/*.jsx\" \"**/*.scss\" --ignore-path .eslintignore --list-different", - "format:fix": "npm run format -- --write", + "format": "prettier --check \"**/*.js\" \"**/*.json\" \"**/*.jsx\" \"**/*.scss\" --ignore-path .eslintignore", + "format:fix": "prettier --write \"**/*.js\" \"**/*.json\" \"**/*.jsx\" \"**/*.scss\" --ignore-path .eslintignore", "lint": "npm-run-all --parallel format lint:*", "lint:css": "stylelint 'nerdlets/**/*.scss' --syntax scss", "lint:css:fix": "npm run lint:css -- --fix", "lint:js": "eslint --ext js,jsx --ignore-path .eslintignore .", "lint:js:fix": "npm run lint:js -- --fix", - "lint:ts": "tsc", "start": "nr1 nerdpack:serve", "test": "npm-run-all --sequential checkversion lint test:js", "test:js": "jest", @@ -29,6 +28,7 @@ "email": "opensource@newrelic.com" }, "dependencies": { + "@newrelic/nr1-community": "^1.2.0", "d3": "^5.12.0", "lodash": "^4.17.15", "prop-types": "^15.6.2", diff --git a/src/components/account-dropdown/index.jsx b/src/components/account-dropdown/index.jsx index f40c187..282b274 100644 --- a/src/components/account-dropdown/index.jsx +++ b/src/components/account-dropdown/index.jsx @@ -6,11 +6,11 @@ import { UserStorageMutation, UserStorageQuery, nerdlet, -} from "nr1"; -import PropTypes from "prop-types"; -import React from "react"; +} from 'nr1'; +import PropTypes from 'prop-types'; +import React from 'react'; -const documentId = "default-account"; +const documentId = 'default-account'; // Account query with extra data than AccountsQuery returns const ACCOUNT_QUERY = ` { @@ -28,6 +28,7 @@ export class AccountDropdown extends React.Component { accountFilter: PropTypes.func, className: PropTypes.any, collection: PropTypes.string, + onLoaded: PropTypes.func, onSelect: PropTypes.func, style: PropTypes.any, title: PropTypes.string, @@ -36,8 +37,8 @@ export class AccountDropdown extends React.Component { static defaultProps = { accountFilter: account => true, - collection: "newrelic", - title: "Select account...", + collection: 'newrelic', + title: 'Select account...', }; constructor(props) { @@ -117,13 +118,18 @@ export class AccountDropdown extends React.Component { const result = await NerdGraphQuery.query({ query: ACCOUNT_QUERY }); const accounts = (((result || {}).data || {}).actor || {}).accounts || []; - this.setState({ - accounts, - accountsById: accounts.reduce((result, account) => { - result[account.id] = account; - return result; - }, {}), - }); + this.setState( + { + accounts, + accountsById: accounts.reduce((result, account) => { + result[account.id] = account; + return result; + }, {}), + }, + () => { + this.props.onLoaded(this.state.accounts); + } + ); } async updateDefaultAccount(account) { @@ -157,16 +163,23 @@ export class AccountDropdown extends React.Component { render() { const { accountFilter, className, style, title } = this.props; const { accounts, defaultAccount, selected } = this.state; + let items; if (!accounts || defaultAccount === undefined) { return ; } - const items = accounts.filter(accountFilter).map(account => ( - this.select(account)}> - {account.name} - - )); + const filteredAccounts = accounts.filter(accountFilter); + + if (accounts && filteredAccounts.length === 0) { + items = No accounts found for this Nerdpack or for your user.; + } else { + items = filteredAccounts.map(account => ( + this.select(account)}> + {account.name} + + )); + } return ( diff --git a/src/components/time-range/index.js b/src/components/time-range/index.js index 273498b..e56aede 100644 --- a/src/components/time-range/index.js +++ b/src/components/time-range/index.js @@ -19,12 +19,12 @@ export const durationToAbsoluteRange = timeRange => { */ export const timeRangeToNrql = timeRange => { if (!timeRange) { - return ""; + return ''; } else if (timeRange.begin_time && timeRange.end_time) { return ` SINCE ${timeRange.begin_time} UNTIL ${timeRange.end_time}`; } else if (timeRange.duration) { return ` SINCE ${timeRange.duration / 1000} SECONDS AGO`; } - return ""; + return ''; }; diff --git a/src/lib/accounts-with-data.js b/src/lib/accounts-with-data.js index 8e257cc..90a4a3f 100644 --- a/src/lib/accounts-with-data.js +++ b/src/lib/accounts-with-data.js @@ -1,4 +1,4 @@ -import { NerdGraphQuery } from "nr1"; +import { NerdGraphQuery } from 'nr1'; /** * For building account pickers, etc. Get the list of all visible accounts that have diff --git a/src/lib/bytes-to-size.js b/src/lib/bytes-to-size.js index 555ac33..bd613df 100644 --- a/src/lib/bytes-to-size.js +++ b/src/lib/bytes-to-size.js @@ -2,25 +2,25 @@ * Convert bytes to human readable bytes */ export const bytesToSize = (bytes, hideUnit) => { - const sizes = ["B", "KB", "MB", "GB", "TB"]; - if (bytes === 0) return `0 ${!hideUnit ? "B" : ""}`; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) return `0 ${!hideUnit ? 'B' : ''}`; const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); - if (i === 0) return `${bytes.toFixed(1)} ${!hideUnit ? sizes[i] : ""}`; - return `${(bytes / 1024 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ""}`; + if (i === 0) return `${bytes.toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; + return `${(bytes / 1024 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; }; export const bitsToSize = (bits, hideUnit) => { - const sizes = ["b", "Kb", "Mb", "Gb", "Tb"]; - if (bits === 0) return `0 ${!hideUnit ? "b" : ""}`; + const sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb']; + if (bits === 0) return `0 ${!hideUnit ? 'b' : ''}`; const i = parseInt(Math.floor(Math.log(bits) / Math.log(1024)), 10); - if (i === 0) return `${bits.toFixed(1)} ${!hideUnit ? sizes[i] : ""}`; - return `${(bits / 1024 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ""}`; + if (i === 0) return `${bits.toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; + return `${(bits / 1024 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; }; export const intToSize = (num, hideUnit) => { - const sizes = ["", "K", "M", "B", "T"]; - if (num === 0) return "0"; + const sizes = ['', 'K', 'M', 'B', 'T']; + if (num === 0) return '0'; const i = parseInt(Math.floor(Math.log(num) / Math.log(1000)), 10); - if (i === 0) return `${num.toFixed(1)} ${!hideUnit ? sizes[i] : ""}`; - return `${(num / 1000 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ""}`; + if (i === 0) return `${num.toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; + return `${(num / 1000 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; }; diff --git a/src/lib/find-related-account-with.js b/src/lib/find-related-account-with.js index d72353c..0bf8a67 100644 --- a/src/lib/find-related-account-with.js +++ b/src/lib/find-related-account-with.js @@ -1,5 +1,5 @@ -import accountsWithData from "./accounts-with-data"; -import nrdbQuery from "./nrdb-query"; +import accountsWithData from './accounts-with-data'; +import nrdbQuery from './nrdb-query'; /** * look across all the accounts the user has access to, scoped to the provided @@ -14,7 +14,7 @@ import nrdbQuery from "./nrdb-query"; * and return the array of accounts, each with a hit count in descending order. */ export default async function findRelatedAccountsWith({ eventType, where, timeWindow }) { - timeWindow = timeWindow || "SINCE 2 minutes ago"; + timeWindow = timeWindow || 'SINCE 2 minutes ago'; const accounts = await accountsWithData(eventType); const nrql = `SELECT count(*) FROM ${eventType} WHERE ${where} ${timeWindow}`; diff --git a/src/lib/nrdb-query.js b/src/lib/nrdb-query.js index e545237..8081c96 100644 --- a/src/lib/nrdb-query.js +++ b/src/lib/nrdb-query.js @@ -1,12 +1,12 @@ -import { NerdGraphQuery } from "nr1"; +import { NerdGraphQuery } from 'nr1'; export default async function nrdbQuery(accountId, nrql) { if (!nrql) { - console.warn("You probably forgot to provide an accountId", accountId); + console.warn('You probably forgot to provide an accountId', accountId); throw nrql; } - nrql = nrql.replace(/\n/g, " "); + nrql = nrql.replace(/\n/g, ' '); const gql = `{ actor { account(id: ${accountId}) { @@ -20,12 +20,12 @@ export default async function nrdbQuery(accountId, nrql) { try { const { data, error } = await NerdGraphQuery.query({ query: gql }); if (error || !data.actor.account.nrql) { - throw new Error("Bad NRQL Query: " + nrql + ": " + error); + throw new Error('Bad NRQL Query: ' + nrql + ': ' + error); } return data.actor.account.nrql.results; } catch (e) { - console.warn("NRDB Query Error", nrql, e); + console.warn('NRDB Query Error', nrql, e); throw e; } } diff --git a/src/lib/nrql.js b/src/lib/nrql.js index c209a40..f6077fd 100644 --- a/src/lib/nrql.js +++ b/src/lib/nrql.js @@ -1,4 +1,4 @@ -import { NerdGraphQuery } from "nr1"; +import { NerdGraphQuery } from 'nr1'; // NerdGraph query wrapper for NRQL export const NERDGRAPH_NRQL_QUERY = ` diff --git a/test/test.test.js b/test/test.test.js index 8492ac7..72fe210 100644 --- a/test/test.test.js +++ b/test/test.test.js @@ -1,3 +1,3 @@ -test("first test!", () => { +test('first test!', () => { expect(true).toBe(true); }); diff --git a/test/utils/setupTests.jsx b/test/utils/setupTests.jsx index f82f441..6c81d3f 100644 --- a/test/utils/setupTests.jsx +++ b/test/utils/setupTests.jsx @@ -1,5 +1,5 @@ -import Adapter from "enzyme-adapter-react-16"; -import { configure } from "enzyme"; +import Adapter from 'enzyme-adapter-react-16'; +import { configure } from 'enzyme'; // Sets up enzyme to know what version of react its working with. configure({ adapter: new Adapter() });