From d6d7f7649bbd7b88d4882c7248b6c38c94095ce4 Mon Sep 17 00:00:00 2001 From: matewilk Date: Mon, 18 Mar 2024 12:07:08 +0000 Subject: [PATCH] feat: separate views from config and componentise --- .../attribute-explorer.js | 387 ++++++------------ .../components/AttributeChart.js | 90 ++++ .../components/AttributeChartsContainer.js | 46 +++ .../components/ChartTile.js | 15 + .../components/CustomDropdown.js | 16 + .../components/HeaderChart.js | 35 ++ .../components/HeaderChartsContainer.js | 88 ++++ nerdlets/nr1-attribute-explorer/index.js | 33 +- .../views/AttributeExplorerView.js | 123 ++++++ 9 files changed, 561 insertions(+), 272 deletions(-) create mode 100644 nerdlets/nr1-attribute-explorer/components/AttributeChart.js create mode 100644 nerdlets/nr1-attribute-explorer/components/AttributeChartsContainer.js create mode 100644 nerdlets/nr1-attribute-explorer/components/ChartTile.js create mode 100644 nerdlets/nr1-attribute-explorer/components/CustomDropdown.js create mode 100644 nerdlets/nr1-attribute-explorer/components/HeaderChart.js create mode 100644 nerdlets/nr1-attribute-explorer/components/HeaderChartsContainer.js create mode 100644 nerdlets/nr1-attribute-explorer/views/AttributeExplorerView.js diff --git a/nerdlets/nr1-attribute-explorer/attribute-explorer.js b/nerdlets/nr1-attribute-explorer/attribute-explorer.js index 3e51095..88cd85d 100644 --- a/nerdlets/nr1-attribute-explorer/attribute-explorer.js +++ b/nerdlets/nr1-attribute-explorer/attribute-explorer.js @@ -1,10 +1,26 @@ -import React from 'react'; -import { Layout, LayoutItem, PlatformStateContext, LineChart, HistogramChart, HeadingText, NrqlQuery, Grid, GridItem, BarChart, - Dropdown, DropdownItem, Button, BlockText, Tile, Spinner, Stack, StackItem, SectionMessage, ChartGroup, navigation } from 'nr1' +import React from "react"; +import { NrqlQuery, navigation } from "nr1"; + +import { AttributeExplorerView } from "./views/AttributeExplorerView"; // A list of attributes that it makes no sense to display and will always be exclude from the UI. These are mainly time based attributes. -const excludeAttributes = ["appId", "duration", "entityGuid", "entity.guid", "nr.guid", "timestamp", "totalTime", "databaseDuration", - "externalDuration", "gcCumulative", "parent.transportDuration", "queueDuration", "totalTime", "webDuration", "duration.ms"]; +const excludeAttributes = [ + "appId", + "duration", + "entityGuid", + "entity.guid", + "nr.guid", + "timestamp", + "totalTime", + "databaseDuration", + "externalDuration", + "gcCumulative", + "parent.transportDuration", + "queueDuration", + "totalTime", + "webDuration", + "duration.ms", +]; /* Main worker class for the AttributeExplorer, called from index.js. @@ -13,7 +29,8 @@ export default class AttributeExplorer extends React.Component { constructor(props) { super(props); // Set the background colour to ensure it looks nice! For some reason this seems to be necessary. - document.getElementById("root").style.backgroundColor = "rgb(241, 242, 242)"; + document.getElementById("root").style.backgroundColor = + "rgb(241, 242, 242)"; //console.log(props); // Save these locally, probably not necessary, but then I am not a React programmer! this.accountId = props.accountId; @@ -29,12 +46,21 @@ export default class AttributeExplorer extends React.Component { this.booleanAttributes = []; // Default Trace filter, based on the entity guid (by default only show traces for the entity). - this.defaultTraceFilter = [{"attr":"entityGuid","operator":"EQ","value":this.guid}]; + this.defaultTraceFilter = [ + { attr: "entityGuid", operator: "EQ", value: this.guid }, + ]; // Current Trace filter, to start with set it to the default. this.traceFilter = this.defaultTraceFilter; // The different metrics we can display for the attributes in the lower section. - this.metric = ["50th percentile", "75th percentile", "90th percentile", "99th percentile", "Average", "Count"]; + this.metric = [ + "50th percentile", + "75th percentile", + "90th percentile", + "99th percentile", + "Average", + "Count", + ]; // Start with the "90th percentile". this.currentMetric = 2; @@ -57,190 +83,40 @@ export default class AttributeExplorer extends React.Component { // Set the default state. Add empty charts as this helps stability of the UI. this.state = { - headerCharts: [, , ], charts: [], metric: this.metric[this.currentMetric], event: this.event[this.currentEvent], }; // Now populate the charts. this.getCharts(true); - }; - - // Method to get and return the 3 headers charts as an array. - // Creating this an array seems to work best! - async getHeaderCharts() { - // The error where clause, which varies depending on the event we are using. - const errorNRQL = [ - ", filter(count(*), WHERE error) as 'Errors'", // Transactions - "", // TransactionError (no where clause here, as all are errors!) - ", filter(count(*), WHERE error.message IS NOT NULL OR error.class IS NOT NULL) as Errors", // Spans (APM) - ", filter(count(*), WHERE otel.status_code = 'ERROR') as Errors"]; // Spans (OTEL) - // The response time query in the first chart. - const responseQuery = "SELECT percentile(" + this.duration[this.currentEvent] + ", 50, 75, 90, 99) as Duration, average(" + - this.duration[this.currentEvent] + ") as 'Avg duration' FROM " + this.eventTypes[this.currentEvent] + " WHERE entityGuid = '" + - this.guid + "'" + this.attribWhere + " TIMESERIES AUTO"; - // The count and error query in the 2nd chart. - const countQuery = "SELECT count(*) as 'Count'" + errorNRQL[this.currentEvent] + " FROM " + - this.eventTypes[this.currentEvent] + " WHERE entityGuid = '" + this.guid + "'" + this.attribWhere + " TIMESERIES AUTO"; - // The histogram query in the 3rd chart. - const histogramQuery = "SELECT histogram(" + this.duration[this.currentEvent] + ", " + this.histogramCeiling + ", 20) as Duration FROM " + - this.eventTypes[this.currentEvent] + " WHERE entityGuid = '" + this.guid + "'" + this.attribWhere; - let headerChartArray = []; - // Push the 3 charts into the array. - // Default the time period to the platformState. - // Refresh once a minute. - headerChartArray.push( - - { - (platformState) => { - return - { - ({data}) => { - return ; - } - } - - } - } - - ); - headerChartArray.push( - - { - (platformState) => { - return - { - ({data}) => { - return ; - } - } - - } - } - - ); - headerChartArray.push( - - { - (platformState) => { - return - { - ({data}) => { - return ; - } - } - - } - } - - ); - return headerChartArray; } // Method to get/refresh the charts async getCharts(getAttrib) { - // String used as a place holder in the NRQL - const attributeString = "ATTRIBUTE"; - // Get the correct NRQL for the duration (OTEL is different from APM). - const dur = this.duration[this.currentEvent]; - // Array of NRQL syntax strings for the metrics that can be selected. - const values = ["percentile(" + dur + ", 50)", "percentile(" + dur + ", 75)", "percentile(" + dur + ", 90)", - "percentile(" + dur + ", 99)", "average(" + dur + ")", "count(*)"]; - // The query we will be using to populate attribuate charts. - const chartQuery = "SELECT " + values[this.currentMetric] + " FROM " + this.eventTypes[this.currentEvent] + - " WHERE entityGuid = '" + this.guid + "'" + this.attribWhere + " FACET `" + attributeString + "`"; // If we have been told to refresh the attribute list (the event type has changed) then do this. if (getAttrib) { await this.getAttributes(); await this.get95Duration(); } - // Loop through all the attributes and build an array of charts - let chartsArray = []; - for (let i = 0; i < this.attributes.length; i++) { - // The time period is set by the platformState.timeRange - // Set the refresh of charts to every minute. - // Show a spinner while the charts are loading. - // Show any errors in place of the charts if we get an error returned. - // Show the text "No values." if no data is returned, rather than an empty chart (this can easily happened when filters are selected). - chartsArray.push( -
{this.attributes[i]}
- - { - (platformState) => { - return - - {({ loading, error, data }) => { - if (loading) { - return ; - } - - if (error) { - return ( - - ); - } - if (data != null && data.length > 0) { - //console.log(this.attributes[i]); - //console.log(data); - return this.setAttribute(this.attributes[i], evt.metadata.name)} - />; - } - else return
No values.
- } - } -
- } - } -
-
); - } - // Get the header charts. - const header = await this.getHeaderCharts(); // Now set the state so the UI will refresh. this.setState({ - charts: chartsArray, metric: this.metric[this.currentMetric], event: this.event[this.currentEvent], - headerCharts: header, }); - }; + } // Method that takes an arrary of attribute names and returns another array of attribute names // excluding any attributes we do not want to display in the charts (mainly response timings). - getAttributesFromArray (attrib) { + getAttributesFromArray(attrib) { let attribArray = []; for (let i = 0; i < attrib.length; i++) { - if (attribArray.indexOf(attrib[i]) < 0 && excludeAttributes.indexOf(attrib[i]) < 0) { + if ( + attribArray.indexOf(attrib[i]) < 0 && + excludeAttributes.indexOf(attrib[i]) < 0 + ) { attribArray.push(attrib[i]); - } + } } return attribArray; } @@ -251,23 +127,34 @@ export default class AttributeExplorer extends React.Component { // can degrade load performance and make it look like nothing is happening! async getAttributes() { // Query to get the attribute list. - const sampleQuery = "SELECT keyset() FROM " + this.eventTypes[this.currentEvent] + " WHERE entityGuid = '" + this.guid + - "' SINCE 1 days ago"; + const sampleQuery = + "SELECT keyset() FROM " + + this.eventTypes[this.currentEvent] + + " WHERE entityGuid = '" + + this.guid + + "' SINCE 1 days ago"; let attribArray = []; // Run and wait for the query. const res = await NrqlQuery.query({ accountIds: [this.accountId], query: sampleQuery, - formatType: NrqlQuery.FORMAT_TYPE.RAW}); + formatType: NrqlQuery.FORMAT_TYPE.RAW, + }); // Save global lists of attributes - all, numbers and booleans, // excluding any unwanted attributes, which we do not want to display in charts. attribArray = this.getAttributesFromArray(res.data.results[0].allKeys); - attribArray.sort(function(a, b){return a.toUpperCase().localeCompare(b.toUpperCase());}); + attribArray.sort(function (a, b) { + return a.toUpperCase().localeCompare(b.toUpperCase()); + }); //console.log(attribArray); this.attributes = attribArray; - this.numberAttributes = this.getAttributesFromArray(res.data.results[0].numericKeys); - this.booleanAttributes = this.getAttributesFromArray(res.data.results[0].booleanKeys); - }; + this.numberAttributes = this.getAttributesFromArray( + res.data.results[0].numericKeys + ); + this.booleanAttributes = this.getAttributesFromArray( + res.data.results[0].booleanKeys + ); + } // Method to get the 95th percentile response time, which is used to set the ceiling of the histogram. // Note, this usees the default time period and is not changed when the user changes the chart time period, @@ -275,13 +162,20 @@ export default class AttributeExplorer extends React.Component { // It would be better if this changed when the user changes the time period, but I haven't found a nice way to do it. async get95Duration() { // Query to get the 95th percentile. - const durationQuery = "SELECT percentile(" + this.duration[this.currentEvent] + ", 95) FROM " + this.eventTypes[this.currentEvent] + - " WHERE entityGuid = '" + this.guid + "'"; + const durationQuery = + "SELECT percentile(" + + this.duration[this.currentEvent] + + ", 95) FROM " + + this.eventTypes[this.currentEvent] + + " WHERE entityGuid = '" + + this.guid + + "'"; // Execute the query and wait for the result. const res = await NrqlQuery.query({ accountIds: [this.accountId], query: durationQuery, - formatType: NrqlQuery.FORMAT_TYPE.RAW}); + formatType: NrqlQuery.FORMAT_TYPE.RAW, + }); //console.log(res); if (res.data.results.length > 0) { // If we got a result, then get it as a number. @@ -290,10 +184,9 @@ export default class AttributeExplorer extends React.Component { if (dur > 1) { dur = Math.round(dur + Number.EPSILON + 0.5); } else if (dur > 0.1) { - dur = Math.round(((dur + Number.EPSILON)*10) + 0.5)/10; - } - else { - dur = Math.round(((dur + Number.EPSILON)*100) + 0.5)/100; + dur = Math.round((dur + Number.EPSILON) * 10 + 0.5) / 10; + } else { + dur = Math.round((dur + Number.EPSILON) * 100 + 0.5) / 100; } //console.log(dur); // Set the global value for the histogram ceiling. @@ -306,7 +199,10 @@ export default class AttributeExplorer extends React.Component { async setAttribute(attrib, val) { // If the attribute is not a number or boolean then we need to be using a quote. let quote = "'"; - if (this.numberAttributes.indexOf(attrib) >= 0 || this.booleanAttributes.indexOf(attrib) >= 0) { + if ( + this.numberAttributes.indexOf(attrib) >= 0 || + this.booleanAttributes.indexOf(attrib) >= 0 + ) { quote = ""; } // Build a local where clause for this attribute and value. @@ -317,13 +213,15 @@ export default class AttributeExplorer extends React.Component { this.attribWhere = this.attribWhere + addWhere; // Now add it to the Trace filters, booleans are a special case. if (this.booleanAttributes.indexOf(attrib) < 0) { - this.traceFilter.push({"attr":attrib,"operator":"EQ","value":val}); + this.traceFilter.push({ attr: attrib, operator: "EQ", value: val }); + } else { + this.traceFilter.push({ + attr: attrib, + operator: val.toUpperCase(), + value: null, + }); } - else { - this.traceFilter.push({"attr":attrib,"operator":val.toUpperCase(),"value":null}); - } - } - else { + } else { // The user is deselecting the attribute and value. // Remove the attribute and value form the global where clause and Trace filters. this.attribWhere = this.attribWhere.replace(addWhere, ""); @@ -367,14 +265,31 @@ export default class AttributeExplorer extends React.Component { async getTraces() { // Create the nerdlet state for the Traces UI, adding the current trace filters. const nerdletState = { - id: 'distributed-tracing-nerdlets.distributed-tracing-launcher', - urlState: {"query":{"operator":"AND","indexQuery":{"conditionType":"INDEX","operator":"AND","conditions":[]}, - "spanQuery":{"operator":"AND","conditionSets":[{"conditionType":"SPAN","operator":"AND", - "conditions":this.traceFilter}]}}}, - } + id: "distributed-tracing-nerdlets.distributed-tracing-launcher", + urlState: { + query: { + operator: "AND", + indexQuery: { + conditionType: "INDEX", + operator: "AND", + conditions: [], + }, + spanQuery: { + operator: "AND", + conditionSets: [ + { + conditionType: "SPAN", + operator: "AND", + conditions: this.traceFilter, + }, + ], + }, + }, + }, + }; //console.log(this.traceFilter); // Display the Traces. - navigation.openStackedNerdlet(nerdletState) + navigation.openStackedNerdlet(nerdletState); } // Main render method. Uses a number of state variables to dynamically render various UI components. @@ -392,70 +307,28 @@ export default class AttributeExplorer extends React.Component { const location = navigation.getOpenStackedNerdletLocation(nerdlet); */ - return (
- - -
- - - - - Performance over time - {this.state.headerCharts[0]} - - - - - Counts over time - {this.state.headerCharts[1]} - - - - - Response time histogram - {this.state.headerCharts[2]} - - - - -
-
-
- -
- - Currently displaying: - - this.setMetric(0)}>{this.metric[0]} - this.setMetric(1)}>{this.metric[1]} - this.setMetric(2)}>{this.metric[2]} - this.setMetric(3)}>{this.metric[3]} - this.setMetric(4)}>{this.metric[4]} - this.setMetric(5)}>{this.metric[5]} - - {this.domain === "APM" && - of: - - this.setEvent(0)}>{this.event[0]} - this.setEvent(1)}>{this.event[1]} - this.setEvent(2)}>{this.event[2]} - - - } - Number of attributes: {this.attributes.length} - -
- - Current filter:{this.attribWhere.replace(/AND/, "")} -
-
-
-
- - -
{this.state.charts}
-
-
-
); + return ( + + ); } } diff --git a/nerdlets/nr1-attribute-explorer/components/AttributeChart.js b/nerdlets/nr1-attribute-explorer/components/AttributeChart.js new file mode 100644 index 0000000..121f4eb --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/components/AttributeChart.js @@ -0,0 +1,90 @@ +// AttributeChart.js +import React from "react"; +import { + PlatformStateContext, + NrqlQuery, + GridItem, + Tile, + HeadingText, + BarChart, + Spinner, + Stack, + StackItem, + SectionMessage, +} from "nr1"; + +const AttributeChartQuery = ({ + accountId, + query, + pollInterval, + timeRange, + onClickBar, +}) => { + return ( + + {({ loading, error, data }) => { + if (loading) { + return ; + } + + if (error) { + return ( + + + + + + ); + } + + if (data != null && data.length > 0) { + return ; + } else { + return
No values.
; + } + }} +
+ ); +}; + +const AttributeChart = ({ + accountId, + attribute, + query, + onClickBar, + pollInterval = 60000, // 60 seconds, +}) => { + return ( + + +
+ + {attribute} + +
+ + {(platformState) => ( + + )} + +
+
+ ); +}; + +export { AttributeChart }; diff --git a/nerdlets/nr1-attribute-explorer/components/AttributeChartsContainer.js b/nerdlets/nr1-attribute-explorer/components/AttributeChartsContainer.js new file mode 100644 index 0000000..2401496 --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/components/AttributeChartsContainer.js @@ -0,0 +1,46 @@ +import React from "react"; +import { Grid } from "nr1"; +import { AttributeChart } from "./AttributeChart"; + +const AttributeChartsContainer = ({ + accountId, + guid, + eventTypes, + currentEvent, + attribWhere, + duration, + currentMetric, + attributes, + setAttribute, +}) => { + // Construct the NRQL query + const values = [ + `percentile(${duration[currentEvent]}, 50)`, + `percentile(${duration[currentEvent]}, 75)`, + `percentile(${duration[currentEvent]}, 90)`, + `percentile(${duration[currentEvent]}, 99)`, + `average(${duration[currentEvent]})`, + "count(*)", + ]; + const attributeString = "ATTRIBUTE"; + const chartQuery = `SELECT ${values[currentMetric]} FROM ${eventTypes[currentEvent]} WHERE entityGuid = '${guid}'${attribWhere} FACET ${attributeString}`; + return ( + + {/* Loop through all the attributes and build an array of charts */} + {attributes.map((attribute) => ( + setAttribute(attribute, evt.metadata.name)} + /> + ))} + + ); +}; + +export { AttributeChartsContainer }; diff --git a/nerdlets/nr1-attribute-explorer/components/ChartTile.js b/nerdlets/nr1-attribute-explorer/components/ChartTile.js new file mode 100644 index 0000000..995ab56 --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/components/ChartTile.js @@ -0,0 +1,15 @@ +import React from "react"; +import { Tile, HeadingText, GridItem } from "nr1"; + +const ChartTile = ({ title, chart, columnSpan }) => { + return ( + + + {title} + {chart} + + + ); +}; + +export { ChartTile }; diff --git a/nerdlets/nr1-attribute-explorer/components/CustomDropdown.js b/nerdlets/nr1-attribute-explorer/components/CustomDropdown.js new file mode 100644 index 0000000..4dc7636 --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/components/CustomDropdown.js @@ -0,0 +1,16 @@ +import React from "react"; +import { Dropdown, DropdownItem } from "nr1"; + +const CustomDropdown = ({ title, options, onSelect, iconType }) => { + return ( + + {options.map((option, index) => ( + onSelect(index)}> + {option} + + ))} + + ); +}; + +export { CustomDropdown }; diff --git a/nerdlets/nr1-attribute-explorer/components/HeaderChart.js b/nerdlets/nr1-attribute-explorer/components/HeaderChart.js new file mode 100644 index 0000000..9f1ac00 --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/components/HeaderChart.js @@ -0,0 +1,35 @@ +import React from "react"; +import { + PlatformStateContext, + NrqlQuery, + LineChart, + HistogramChart, +} from "nr1"; + +const HeaderChart = ({ accountId, query, chartType, pollInterval = 60000 }) => { + return ( + + {(platformState) => ( + + {({ data }) => { + switch (chartType) { + case "line": + return ; + case "histogram": + return ; + default: + return null; + } + }} + + )} + + ); +}; + +export { HeaderChart }; diff --git a/nerdlets/nr1-attribute-explorer/components/HeaderChartsContainer.js b/nerdlets/nr1-attribute-explorer/components/HeaderChartsContainer.js new file mode 100644 index 0000000..f84d6dc --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/components/HeaderChartsContainer.js @@ -0,0 +1,88 @@ +import React, { useEffect, useState } from "react"; +import { Grid, ChartGroup } from "nr1"; +import { HeaderChart } from "./HeaderChart"; +import { ChartTile } from "./ChartTile"; + +const HeaderChartsContainer = ({ + accountId, + guid, + eventTypes, + currentEvent, + attribWhere, + duration, + histogramCeiling, +}) => { + const [responseQuery, setResponseQuery] = useState(""); + const [countQuery, setCountQuery] = useState(""); + const [histogramQuery, setHistogramQuery] = useState(""); + + useEffect(() => { + const errorNRQL = [ + ", filter(count(*), WHERE error) as 'Errors'", // Transactions + "", // TransactionError (no where clause here, as all are errors!) + ", filter(count(*), WHERE error.message IS NOT NULL OR error.class IS NOT NULL) as Errors", // Spans (APM) + ", filter(count(*), WHERE otel.status_code = 'ERROR') as Errors", // Spans (OTEL) + ]; + + // The response time query for the first chart. + const newResponseQuery = `SELECT percentile(${duration[currentEvent]}, 50, 75, 90, 99) as Duration, average(${duration[currentEvent]}) as 'Avg duration' FROM ${eventTypes[currentEvent]} WHERE entityGuid = '${guid}'${attribWhere} TIMESERIES AUTO`; + // The count and error query for the second chart. + const newCountQuery = `SELECT count(*) as 'Count'${errorNRQL[currentEvent]} FROM ${eventTypes[currentEvent]} WHERE entityGuid = '${guid}'${attribWhere} TIMESERIES AUTO`; + // The histogram query for the third chart. + const newHistogramQuery = `SELECT histogram(${duration[currentEvent]}, ${histogramCeiling}, 20) as Duration FROM ${eventTypes[currentEvent]} WHERE entityGuid = '${guid}'${attribWhere}`; + + setResponseQuery(newResponseQuery); + setCountQuery(newCountQuery); + setHistogramQuery(newHistogramQuery); + }, [ + accountId, + guid, + eventTypes, + currentEvent, + attribWhere, + duration, + histogramCeiling, + ]); + + return ( + + + + } + columnSpan={5} + /> + + } + columnSpan={4} + /> + + } + columnSpan={3} + /> + + + ); +}; + +export { HeaderChartsContainer }; diff --git a/nerdlets/nr1-attribute-explorer/index.js b/nerdlets/nr1-attribute-explorer/index.js index eb9a573..8144826 100644 --- a/nerdlets/nr1-attribute-explorer/index.js +++ b/nerdlets/nr1-attribute-explorer/index.js @@ -1,6 +1,12 @@ -import React from 'react'; -import { NerdletStateContext, EntityByGuidQuery, Spinner, StackItem, SectionMessage } from 'nr1'; -import AttributeExplorer from './attribute-explorer'; +import React from "react"; +import { + NerdletStateContext, + EntityByGuidQuery, + Spinner, + StackItem, + SectionMessage, +} from "nr1"; +import AttributeExplorer from "./attribute-explorer"; /* Main parent class, checks for an entity guid and calls the main class (AttributeExplorer). @@ -13,11 +19,10 @@ export default class Nr1AttributeExplorerNerdlet extends React.Component { // Method to get and return the entity guid from the nerdlet state. getEntityGuid(nerd) { - if (nerd.hasOwnProperty('entityGuid')) { + if (nerd.hasOwnProperty("entityGuid")) { console.log(nerd); return nerd.entityGuid; - } - else { + } else { return null; } } @@ -27,7 +32,7 @@ export default class Nr1AttributeExplorerNerdlet extends React.Component { render() { return ( - {(nerdletState) => + {(nerdletState) => ( {({ loading, error, data }) => { if (loading) { @@ -38,20 +43,18 @@ export default class Nr1AttributeExplorerNerdlet extends React.Component { return ( - + ); } if (data.entities.length === 0) { return ( - + ); } @@ -67,8 +70,8 @@ export default class Nr1AttributeExplorerNerdlet extends React.Component { ); }} - } + )} ); - } + } } diff --git a/nerdlets/nr1-attribute-explorer/views/AttributeExplorerView.js b/nerdlets/nr1-attribute-explorer/views/AttributeExplorerView.js new file mode 100644 index 0000000..ed301af --- /dev/null +++ b/nerdlets/nr1-attribute-explorer/views/AttributeExplorerView.js @@ -0,0 +1,123 @@ +import React from "react"; +import { Layout, LayoutItem, Tile, BlockText, Button, Dropdown } from "nr1"; +import { HeaderChartsContainer } from "../components/HeaderChartsContainer"; +import { AttributeChartsContainer } from "../components/AttributeChartsContainer"; +import { CustomDropdown } from "../components/CustomDropdown"; + +const AttributeExplorerView = ({ + accountId, + guid, + eventTypes, + currentEvent, + attribWhere, + duration, + histogramCeiling, + metric, + metricOptions, + event, + eventOptions, + attributes, + currentMetric, + setMetric, + setEvent, + clearFilter, + getTraces, + setAttribute, + domain, +}) => { + return ( +
+ + +
+ +
+
+
+ + +
+ + + Currently displaying: + + {domain === "APM" && ( + + of: + + + )} + Number of attributes: {attributes.length} + +
+ + + Current filter:{attribWhere.replace(/AND/, "")} + +
+
+
+
+
+
+ + +
+ +
+
+
+
+ ); +}; + +export { AttributeExplorerView };